Fiber(React Fiber 架构)

React 16+ 的核心数据结构和调度架构,将组件树表示为链表结构的 Fiber 节点,使渲染工作可被拆分为小单元(work unit),实现可中断渲染、优先级调度和时间切片。

核心概念

Fiber 是 React 16 引入的重写架构,解决了 React 15 Stack Reconciler 的根本问题——递归 diff 不可中断,大组件树的 diff 会长时间阻塞主线程。

Fiber 的核心设计:

  • 数据结构:每个组件/DOM 节点对应一个 Fiber 节点,节点通过 childsiblingreturn 三个指针组成链表树。链表结构的关键优势是遍历可以在任意节点暂停和恢复——这是 Stack Reconciler 的递归调用栈做不到的
  • 工作单元:每个 Fiber 节点的处理(beginWorkcompleteWork)是一个独立的 work unit,处理完一个节点后可以检查是否需要让出主线程
  • 双缓冲:维护 current 树和 workInProgress 树两份 Fiber 树。更新时在 workInProgress 上操作,commit 阶段原子性地切换指针,保证用户看到的 DOM 始终一致

Fiber 节点的关键字段

text
FiberNode {
  tag          // 节点类型(FunctionComponent、HostComponent 等)
  type         // 组件函数或 DOM 标签名
  stateNode    // 真实 DOM 节点引用
  memoizedState // hooks 链表头指针
  memoizedProps // 上次渲染的 props
  pendingProps  // 待处理的新 props
  flags        // 副作用标记(Placement、Update、Deletion 等)
  lanes        // 该节点待处理的更新优先级
  child / sibling / return  // 链表指针
  alternate    // 指向另一棵树中的对应节点(双缓冲)
}

渲染流程

Fiber 架构将渲染分为两个阶段:

Render 阶段(可中断)

  1. 调度器从根节点开始,选择当前最高优先级的 lanes 子集
  2. 选择 workLoopSync(不可中断)或 workLoopConcurrent(可中断)——这是调度器的最后一个决策点
  3. performUnitOfWork 逐个处理 Fiber 节点:beginWork 执行组件函数/diff 子节点,completeWork 收集 DOM 操作
  4. 并发模式下,每个 work unit 结束后检查 shouldYield(),如有更高优先级任务则让出主线程

Commit 阶段(不可中断)

  1. beforeMutation:执行 DOM 读操作(如 getSnapshotBeforeUpdate
  2. mutation:执行 DOM 写操作(插入、更新、删除),纯写阶段,不可插入读操作
  3. layout:执行 useLayoutEffect,此时 DOM 已更新,允许开发者同步读写 DOM(如测量元素尺寸后调整布局)

两层读写分离大幅降低了 layout thrashing 风险:beforeMutation(读)和 mutation(写)之间实现了完全的读写隔离,消除了这两个阶段内的 forced synchronous layout;layout 阶段是一个受控的"读写混合区",但它发生在所有 mutation 完成之后,最多触发一次额外 layout 计算,不会产生交替 thrashing(O(N) 次 forced layout 的性能灾难)。相比之下,Solid 的 effect 内部没有架构级读写分离,开发者在 effect 中交替读写 DOM 会直接触发 forced layout。

注意:Render 阶段的可中断调度(时间切片 / Lanes 优先级)和 Commit 阶段的读写分离是两个正交的优化——前者解决"长 render 任务不卡主线程",后者解决"DOM 操作不触发 forced layout"。

与其他框架的对比

Fiber 架构的复杂度是 React 响应式模型的必然结果。React 没有细粒度状态追踪,setState 触发的更新可能涉及整棵子树的重新执行和 diff——工作量天然较大。Fiber 的可中断渲染和时间切片本质上是在说:"既然每次更新都很重,至少让我们把重活拆开做,别一口气卡住主线程。"

同时,较长的渲染管线也提供了架构空间来实现 commit 三阶段的读写分离——管线越长,越有余地在管线内部做精细的阶段划分。Solid 和 Svelte 的管线极短(signal → effect → DOM 操作),没有架构空间插入独立的读写分离阶段。管线长度不只是性能开销,也是优化能力。

Vue 选择了不同的折中——响应式追踪到属性级,组件级重渲染的工作量通过 Patch Flags 控制在较低水平,微任务队列批处理已足够,不需要 Fiber 级别的可中断调度。Vue 的 nextTick 提供了"建议性"的读写分离时机,但不像 React 那样在架构层面强制隔离。

卸载路径的复杂度

Fiber 的卸载(commitDeletionEffects)需要递归遍历整棵 Fiber 子树,按 child → sibling → return 顺序逐个触发 useEffect 的 cleanup、ref 清理、Suspense 边界处理等。这比 Solid 的 owner 树 dispose(直接调 onCleanup 回调)和 Vue 的 VNode 递归 unmount 都要复杂——因为 Fiber 节点上挂载的状态种类最多(hooks 链表、effect 链、ref、context 等)。

引用本术语的文章