人人都会AI编程

Fiber 节点的数据结构

更新时间:2026-07-10

Fiber 架构的核心是 Fiber 节点。React 在运行时会将每个组件、每个 HTML 元素都抽象成一个 Fiber 对象,从而形成一个可遍历、可中断的 Fiber 树。理解 Fiber 节点的数据结构,是掌握 React 底层渲染机制的关键一步。

Fiber 节点的“身份证”:一个 JavaScript 对象

Fiber 节点本质上是一个普通的 JavaScript 对象,内部包含了几十个属性。我们可以将这些属性按功能大致分为三类:描述节点信息、构建树形关系、记录工作进度。

下面是 Fiber 节点核心属性的简化定义:

type Fiber = {
  // 节点身份标识
  tag: WorkTag,          // 节点类型(函数组件、类组件、原生标签等)
  key: null | string,     // 列表中的 key,用于 Diff
  elementType: mixed,     // 原始类型,如 "div" 或 组件函数
  type: mixed,            // 经过包装后的类型,通常与 elementType 相同

  // 树形关系(重要!)
  return: Fiber | null,   // 指向父节点
  child: Fiber | null,    // 指向第一个子节点
  sibling: Fiber | null,  // 指向下一个兄弟节点

  // 工作进度与状态
  pendingProps: mixed,    // 本轮渲染的新 Props
  memoizedProps: mixed,   // 上一轮渲染后的 Props
  memoizedState: mixed,   // 上一次渲染时的状态(类组件为 state,函数组件为 Hooks 链表)
  updateQueue: mixed,     // 状态更新队列
  flags: Flags,           // 副作用标记(插入、更新、删除等)
  subtreeFlags: Flags,    // 子树中存在的副作用标记

  // 双缓存
  alternate: Fiber | null, // 指向另一个缓冲树中的对应节点

  // 调度优先级
  lanes: Lanes,           // 节点涉及的更新优先级
  childLanes: Lanes,      // 子树中存在的更新优先级

  //... 还有更多内部属性
};

构建 Fiber 树的三个关键属性:returnchildsibling

Fiber 节点通过三个指针编织成一个树状结构,而且支持高效的单向遍历:

  • return:指向父节点。每当当前节点处理完成后,React 会沿着 return 回到父节点,继续处理父节点的下一个兄弟。
  • child:指向第一个子节点。当进入一个节点时,React 会先处理它的第一个子节点(深度优先)。
  • sibling:指向右侧的兄弟节点。当所有子节点处理完毕后,React 通过 sibling 向右移动,处理同级的下一个节点。

这种结构将传统的多叉树转换为类似二叉树的链表结构,遍历时不需要额外的栈或递归,只需按固定的顺序移动指针(先 child,再 sibling,再 return),非常适合可中断的“时间切片”机制。

举个可视化的例子,假设我们有这样一段 JSX:

<div>
  <h1>标题</h1>
  <p>段落</p>
</div>

对应的 Fiber 树关系如下(箭头方向代表指针指向):

Fiber(div)
  │ child
  ▼
Fiber(h1) ── sibling ──► Fiber(p)
  │                        │
  └── return ──────────────┘
  • divchild 指向 h1(第一个子节点)。
  • h1sibling 指向 p(下一个兄弟节点)。
  • h1preturn 都指回 div(父节点)。
  • 文本节点“标题”和“段落”也会包装成 Fiber 节点,作为 h1pchild,这里省略以显清晰。

工作进度记录:memoizedStatememoizedPropsupdateQueue

Fiber 节点不仅记录结构,还保存渲染过程中的“状态快照”和“待办事项”:

  • memoizedProps:上一次提交到真实 DOM 时的 Props。在 Render 阶段,新 Props(pendingProps)会与之比较,决定组件是否需要更新。
  • memoizedState:上一次提交时的状态。对于函数组件,这个字段不是简单的值,而是一个 Hooks 链表头。每个 Hook 对应的值(如 useState 的数据、useReducer 的数据)都串联在这个链表上,调用顺序决定了链表节点的位置。
  • updateQueue:存储状态更新任务。例如 setState 传进来的参数会被加入一个循环链表,然后在 Render 阶段被依次处理。这解释了为什么多次调用 setState 可以被合并:它们都暂存在同一个 updateQueue 中。
  • flags(旧称 effectTag:标记当前节点需要执行的 DOM 操作,比如插入(Placement)、更新(Update)、删除(Deletion)。Commit 阶段会遍历这些 Flag 并执行对应操作。

双缓存的关键:alternate 属性

React 同时维护两棵 Fiber 树:current 树(当前屏幕上对应的树)和 workInProgress 树(正在内存中构建的新树)。alternate 属性就是这两棵树中对应节点的相互引用。

  • 当开始一次新渲染时,React 会为每个 current 节点克隆一个 workInProgress 节点,并将双方通过 alternate 互指。
  • 在 Render 阶段,所有工作都在 workInProgress 树上进行,current 树保持不变(确保屏幕上的内容不受影响)。
  • Commit 阶段完成后,workInProgress 树会变成新的 current 树,旧的 current 树则被标记为“可回收”。

这种双缓冲机制使得并发渲染成为可能:一个高优先级的更新可以打断正在进行的低优先级渲染,因为 current 树负责显示,workInProgress 树可以随时丢弃并从头开始。

节点类型标识:tag 属性

tag 是一个数值,用于区分 Fiber 节点的种类。React 对不同种类的节点处理逻辑不同。常见的 tag 类型包括:

| tag 值 | 含义 |
|--------|------|
| FunctionComponent | 函数组件 |
| ClassComponent | 类组件 |
| HostComponent | 原生 DOM 元素(如 <div><span>) |
| HostText | 文本节点 |
| Fragment | <Fragment><>...</> |
| HostRoot | 容器的根节点(ReactDOM.createRoot 的宿主) |
| ForwardRef | forwardRef 包裹的组件 |

React 在协调(Reconciliation)时会根据 tag 执行不同的处理分支,例如函数组件需要执行 Hook 调用,类组件需要实例化并调用生命周期,原生 DOM 标签则直接生成对应的元素。

为什么这么设计:为“可中断渲染”而生

Fiber 节点的数据结构并非随意设计,而是为了满足两个核心需求:

  1. 可中断的深度优先遍历:通过 return-child-sibling 形成链表,React 可以在任何 Fiber 节点暂停工作,并在浏览器空闲时继续。传统递归无法做到这一点。
  2. 最小化的状态回溯:每个节点都记录了当前渲染过程中的临时状态(pendingPropsupdateQueue 等),即使工作被打断,也能根据这些记录重新开始,无需重头再来。

这种精妙的数据结构是 React 并发能力的基石。理解了 Fiber 节点,你就理解了 React 如何在“快”和“稳”之间取得平衡——既保证了大型应用的流畅性,又不牺牲开发体验。