React 16 引入的 Fiber 架构,是对原有 Stack Reconciler 的彻底重写,旨在解决同步递归更新导致的主线程长时间占用问题。Fiber 将渲染任务拆解为可中断的小工作单元,使 React 能够实现异步可中断的渲染。下面深入到 Fiber 的三个核心设计。
5.2.1 Fiber 节点的数据结构
在 React 内部,每个组件实例或 DOM 节点都对应一个 Fiber 节点。它是一个普通的 JavaScript 对象,承载了调度与恢复所需的关键信息。其核心结构可简化如下:
function FiberNode(tag, pendingProps, key) {
// 静态结构属性
this.tag = tag; // 节点类型,如 FunctionComponent、ClassComponent、HostComponent
this.key = key; // 节点唯一标识
this.elementType = null; // 原始 ReactElement 类型
this.type = null; // 函数组件本身 或 宿主元素的字符串类型(如'div')
this.stateNode = null; // 对应的真实 DOM 或组件实例
// 树结构连接
this.return = null; // 父 Fiber
this.child = null; // 第一个子 Fiber
this.sibling = null; // 右侧兄弟 Fiber
this.index = 0; // 在父节点中的索引
// 工作相关
this.pendingProps = pendingProps; // 待处理的 props
this.memoizedProps = null; // 上次渲染的 props
this.memoizedState = null; // 上次渲染的 state(Hooks 链表挂载于此)
this.updateQueue = null; // 更新队列
// 副作用标记
this.flags = NoFlags; // 操作标记,如 Placement、Update、Deletion
this.subtreeFlags = NoFlags; // 子树的副作用标记
this.deletions = null; // 需要删除的子 Fiber
// 调度优先级相关
this.lanes = NoLanes; // 该 Fiber 的更新优先级
this.childLanes = NoLanes; // 子树的更新优先级
}
关键点:
- Fiber 节点同时承担 数据存储、调度单元、渲染工作项 三重角色。
memoizedState对于函数组件而言,就是 Hooks 链表的头节点,这也是为什么 Hooks 必须在顶层调用且不能放在条件语句中:React 依赖固定的链表顺序来匹配状态。flags是 Commit 阶段识别需要操作真实 DOM 的标记,避免了额外遍历。
5.2.2 双缓存技术:current 树与 workInProgress 树
Fiber 架构内部同时维护两棵 Fiber 树:
- current 树(当前树)
对应屏幕上已经渲染出来的 UI,其 alternate 属性指向正在构建的 workInProgress 树。
- workInProgress 树(工作进展树)
是正在内存中构建的新 Fiber 树,用于反映即将呈现的 UI,其 alternate 属性指回 current 树。
工作流程:
- 当一次更新触发后,React 从 current 树的根节点(hostRoot)开始,调用
createWorkInProgress创建根节点的 workInProgress 副本,复用原有节点的真实 DOM 或实例(通过alternate引用),并重置一些属性。
- 在 Render 阶段,React 遍历组件树,为每个节点创建或复用 workInProgress Fiber,根据新的 state/props 计算子节点,形成新的 workInProgress 树。期间会对节点进行 diff 并标记
flags。
- Commit 阶段完成后,React 直接交换两棵树的指针:将 workInProgress 树“提升”为新的 current 树,之前的 current 树变为下一轮更新的备用树。
双缓存的意义:
- 无缝替换:切换只是一次指针的赋值,无需销毁和重建 DOM,实现了原子性视图更新。
- 中断恢复:由于工作进度始终在 workInProgress 树上,即使 Render 阶段被中断,也不会污染当前屏幕显示的 current 树。恢复时可直接基于上一次的进度继续构建。
current workInProgress
(屏幕呈现) (内存构建)
| |
child ← return → sibling ←→ child ← return → sibling
| |
alternate ---------- alternate
5.2.3 时间切片(Time Slicing):可中断的渲染过程
传统同步递归一旦开始渲染整棵树,就必须一气呵成,在此期间浏览器无法响应用户输入或动画,造成明显的卡顿。Fiber 架构将整个 Render 阶段变为可中断的循环,核心机制就是时间切片。
- 工作单元:每个 Fiber 节点的处理(
beginWork+completeWork)构成一个最小的工作单元。 - 协作式调度:React 利用浏览器的
MessageChannel或requestAnimationFrame(旧方案)来检测当前帧剩余时间。当单帧时间(约 16.6ms)有剩余时,会继续处理工作单元;若时间耗尽,则将控制权交还给浏览器。 - 中断与恢复:一旦中断,React 记录下当前处理到的 Fiber 节点(
.return链帮助回溯),下一次主线程空闲时会从该节点继续工作,直到所有工作完成。
实际效果:
// 同时有多个更新发生时,高优先级更新可以打断低优先级的渲染
function App() {
const [input, setInput] = useState('');
const list = useSlowList(input); // 假设这是个耗时的渲染列表
return (
<div>
<input onChange={e => setInput(e.target.value)} />
<SlowList list={list} />
</div>
);
}
在 React 18 的并发模式下,用户输入触发的更新优先级高于列表渲染。Render 阶段如果正在构建列表的 Fiber 树,浏览器可以中断该工作去处理更紧急的输入,响应完成后继续完成剩余的列表渲染。这种能力让交互始终保持流畅,即使页面存在大量渲染工作。
注意:Commit 阶段永远是同步且不可中断的,因为它需要将最终变更应用到真实 DOM,必须在一个帧内完成以避免出现 UI 不一致。但 Render 阶段可中断的设计已经极大地改善了性能体验。
综上,Fiber 通过可扩展的节点结构、双缓存无缝切换和时间切片可中断机制,为 React 的异步渲染和并发模式奠定了基石。理解这三者是掌握 React 底层运行逻辑的关键。