Skip to content

Fiber 数据结构与 render/commit 源码主线 ​

如果说 React Element 解决的是“输入长什么样”,那 Fiber 解决的就是 React 如何把一棵组件树的更新工作组织起来,并一步步算到最终结果。

这篇文章重点讲:

  1. Fiber 节点到底是什么
  2. 为什么它不是“另一个虚拟 DOM”
  3. beginWork、completeWork、commit 各自负责什么
  4. 为什么 React 会用 child / sibling / return 这套结构

1. Fiber 到底是什么 ​

Fiber 可以看成 React 内部用来表示一个更新单元和一段节点工作状态的数据结构。

把它和 React Element 的关系拉开:

层更像什么
React Element一份轻量的 UI 描述对象
Fiber一份带有工作状态、更新信息、树关系的可执行节点

也就是说:

  1. Element 更偏“描述要渲染什么”
  2. Fiber 更偏“这次更新要怎么处理这个节点”

🌟 Fiber 的重点不是“再造一份树”,而是 把节点、更新、优先级、副作用和树关系统一挂到一个可调度的数据结构上。


1.1 Fiber 的完整释义和主要能力边界 ​

如果只把 Fiber 解释成“更细粒度调度”,还是太薄。

更完整的理解应该是:

Fiber 是 React 在调和阶段使用的一种工作节点结构。它既表示当前树上的一个节点,又记录这个节点这轮更新要处理的输入、已有状态、需要执行的副作用,以及它和父子兄弟节点之间的关系。

它真正负责的是:

  1. 承接 React Element 转下来的节点信息
  2. 保存本轮更新的工作状态
  3. 连接树结构,方便逐步遍历
  4. 保存更新队列、flags、副作用等运行期信息
  5. 让 render 阶段和 commit 阶段可以衔接

它不负责的是:

  1. 不直接等于真实 DOM
  2. 不单独决定任务什么时候执行
  3. 不等于 React Element 本身

所以把这两个概念放回原位看:

概念主要职责主要边界
React Element描述输入不保存运行期工作状态
Fiber组织节点工作与树关系不直接落地宿主环境
scheduler安排任务时机与优先级不理解组件树细节
DOM页面真实结果不负责 React 调和逻辑

这张表最好记,因为它能直接回答“Fiber 到底是不是虚拟 DOM”“Fiber 到底是不是调度器”这些高频误解。


2. Fiber 节点长什么样 ​

下面是一份精简后的结构示意,不是 React 仓库里的逐字符原样代码。

js
function FiberNode(tag, pendingProps, key) {
  this.tag = tag;
  this.key = key;
  this.type = null;
  this.stateNode = null;

  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.memoizedState = null;
  this.updateQueue = null;

  this.flags = 0;
  this.subtreeFlags = 0;

  this.alternate = null;
}

这里最值得先认识的是这几组字段:

  1. tag / type:当前节点是什么类型
  2. child / sibling / return:树结构关系
  3. pendingProps / memoizedProps:本轮待处理 props 和上次记忆值
  4. memoizedState / updateQueue:状态与更新队列
  5. flags:这个节点后续 commit 时要做什么
  6. alternate:当前树和工作中树之间的对应关系

3. 为什么不是传统“数组 children”结构 ​

React 在 Fiber 上更常见的是:

  1. child
  2. sibling
  3. return

而不是简单嵌套 children 数组直接一路递归到底。

这套结构的好处主要是:

  1. 更适合把树变成“链表式可遍历结构”
  2. 更方便中断、恢复和继续处理
  3. 更方便在遍历时记录父子兄弟关系

Fiber 不是只为了表达树,而是为了表达“可被逐步处理的树”。


4. alternate 在解决什么问题 ​

这是读 Fiber 时非常关键的字段。

它主要用来连接两棵对应节点:

  1. 当前已经提交到页面上的 Fiber 树
  2. 当前这轮正在计算中的 work-in-progress Fiber 树

React 更新时,并不是直接在当前树上随便乱改,而更常见的是:

  1. 基于旧 Fiber 创建或复用 work-in-progress Fiber
  2. 在新树上计算这轮更新
  3. 完成后再切换成最新树

这样做能让 render 阶段和 commit 阶段的职责更清楚。


4.1 alternate 的能力边界 ​

alternate 的价值很大,但它负责的事情也很明确。

它主要负责:

  1. 连接 current Fiber 和 work-in-progress Fiber
  2. 让 React 在新旧两棵工作树之间建立一一对应关系
  3. 让 render 阶段可以基于旧节点复用已有信息

它不负责:

  1. 不直接比较差异
  2. 不直接决定是否 commit
  3. 不单独承载副作用逻辑

所以 alternate 更像“新旧工作节点之间的桥”,而不是“更新决策器”。


5. render 阶段主线:beginWork 和 completeWork ​

如果把 render 阶段压缩成最值得先记的两步,就是:

  1. beginWork
  2. completeWork

5.1 beginWork 在做什么 ​

它更偏“向下”处理。

常见职责包括:

  1. 看当前 Fiber 类型
  2. 比较本轮输入有没有变化
  3. 决定是否可以 bailout
  4. 生成或复用子 Fiber

beginWork 的重点,是决定这个节点和它的子树接下来怎么继续展开。

5.2 completeWork 在做什么 ​

它更偏“向上”收尾。

常见职责包括:

  1. 补齐当前 Fiber 的完成态信息
  2. 收集子树副作用标记
  3. 为宿主节点准备 commit 所需信息

可以把它看成:beginWork 像往下拆任务,completeWork 像往上收结果。


5.3 beginWork、completeWork、commit 的边界怎么分 ​

这 3 个词如果不放在一起看,很容易全混。

beginWork ​

更偏“继续展开这棵子树”。

它主要负责:

  1. 判断当前节点该不该继续处理
  2. 根据 type 和输入决定走哪条分支
  3. 生成或复用子 Fiber

它不主要负责:

  1. 不直接落地 DOM
  2. 不做最终副作用执行

completeWork ​

更偏“把当前节点这轮 render 的结果收口”。

它主要负责:

  1. 补齐完成态信息
  2. 向上汇总子树副作用标记
  3. 为 commit 做准备

它不主要负责:

  1. 不直接执行 effect
  2. 不直接操作宿主环境

commit ​

更偏“把 render 算好的结果真正生效”。

它主要负责:

  1. 执行插入、更新、删除
  2. 处理 ref
  3. 执行布局和被动副作用

它不主要负责:

  1. 不重新计算整棵树
  2. 不重跑 diff 主逻辑

🌟 一句话压缩就是:beginWork 负责向下算,completeWork 负责向上收,commit 负责真正落地。`


6. 一段精简后的 work loop 示意 ​

下面这段代码不是 React 源码原文,而是保留核心结构后的阅读辅助版本。

js
function workLoop() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  const next = beginWork(unitOfWork);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork;

  do {
    completeWork(completedWork);

    if (completedWork.sibling !== null) {
      workInProgress = completedWork.sibling;
      return;
    }

    completedWork = completedWork.return;
    workInProgress = completedWork;
  } while (completedWork !== null);
}

这段代码最值得注意的不是语法细节,而是遍历顺序:

  1. 先往下走到子节点
  2. 子节点做完后再回到父节点
  3. 再处理兄弟节点

这也是为什么 Fiber 会同时保留 child / sibling / return。


7. commit 阶段在做什么 ​

render 阶段做完后,React 还不会立刻把所有结果落到 DOM。

还要进入 commit 阶段。

commit 的重点是:

  1. 遍历带有副作用标记的 Fiber
  2. 执行插入、更新、删除
  3. 处理 ref
  4. 执行布局相关和被动副作用

把 render 和 commit 拆开看:

  1. render 阶段更偏“计算”
  2. commit 阶段更偏“真正生效”

8. flags 为什么重要 ​

Fiber 上的 flags 很值得关注。

它更像是 这个节点在 commit 阶段需要做哪些动作的标记集合。

例如概念上常见的几类动作:

  1. 插入
  2. 更新
  3. 删除
  4. ref 相关处理
  5. effect 相关处理

所以 commit 阶段不是重新猜要干什么,而是根据 render 阶段已经收集好的标记去执行。


8.1 flags 的边界 ​

flags 经常会被误解成“React 的全部副作用系统”。

它本质上只是:

  1. 一组提交阶段动作标记
  2. 用来告诉 commit 当前节点需要做哪些事

它不等于:

  1. 具体的 DOM 实例
  2. 完整的调度优先级信息
  3. Element 输入数据本身

所以 flags 负责的是“告诉 commit 做什么”,而不是“自己把事情做完”。


9. 一张图把 Fiber 主线串起来 ​

mermaid
flowchart TD
    A[React Element] --> B[创建 / 复用 Fiber]
    B --> C[beginWork]
    C --> D[继续向下生成子 Fiber]
    D --> E[completeWork]
    E --> F[向上收集 flags]
    F --> G[commitRoot]
    G --> H[DOM 变更 / ref / effect]

10. 常见相关源码文件 ​

如果你继续往仓库里读,最常见会遇到这些文件:

文件主要职责
packages/react-reconciler/src/ReactFiber.jsFiber 节点定义
packages/react-reconciler/src/ReactFiberBeginWork.jsrender 阶段向下处理
packages/react-reconciler/src/ReactFiberCompleteWork.jsrender 阶段向上收尾
packages/react-reconciler/src/ReactFiberWorkLoop.jswork loop 主循环
packages/react-reconciler/src/ReactFiberCommitWork.jscommit 相关逻辑

建议先重点看:

  1. Fiber 上有哪些关键字段
  2. performUnitOfWork 怎么串 beginWork
  3. completeUnitOfWork 怎么回溯父兄节点
  4. commit 为什么不和 render 混在一起

11. 这一层最常见的误区 ​

11.1 以为 Fiber 就是虚拟 DOM 2.0 ​

不够准确。

Fiber 的重点是更新工作的数据结构和调度模型。

11.2 以为 render 阶段已经改了 DOM ​

不是。

真正落地通常在 commit。

11.3 以为 child / sibling / return 只是换个写法 ​

它更关键的价值是让树变成更适合逐步遍历和调度的结构。


12. 一句话总结 ​

Fiber 这一层的源码主线,本质上是在做 把 React Element 转成可执行的 Fiber 树,在 render 阶段通过 beginWork 和 completeWork 逐步计算下一棵树,再在 commit 阶段根据 flags 把最终结果提交到宿主环境。

基于 VitePress 构建的个人技术笔记。