Read OSS

Fiber 数据结构 — React 的内部表示

高级

前置知识

  • 第 1 篇:架构概览(了解各包的位置以及 fork 系统的工作原理)
  • JavaScript 位运算(&、|、~、>>>、clz32)
  • 树形数据结构与链表遍历

Fiber 数据结构 — React 的内部表示

你写的每一个 React 组件、渲染的每一个 <div>、包裹的每一个 Suspense 边界,在运行时都会变成一个 Fiber 节点。Fiber 本质上是一个普通的 JavaScript 对象(出于 V8 形状优化的考虑,实际上通过构造函数创建),它承载了 React 处理一个工作单元所需的一切信息:类型、props、state、在树中的位置,以及需要提交到宿主环境的副作用。

理解 Fiber 类型是本系列后续内容的前置知识。工作循环遍历 fiber,Hooks 将状态存储在 fiber 上,提交阶段从 fiber 读取 flags。如果说 fiber 是 React 运行时宇宙的原子,那本篇就是你的元素周期表。

Fiber 类型定义

完整的 Fiber 类型定义在 ReactInternalTypes.js 中。下面按逻辑分组逐一介绍各个字段。

标识字段 — 这个 fiber 是什么?

字段 类型 用途
tag WorkTag 数字类型标识符(FunctionComponent、HostComponent 等)
key ReactKey 用户提供的、用于协调的 key
elementType any React 元素中原始的 type(协调时用于保持引用稳定)
type any 已解析的函数/类/字符串——热更新时可能与 elementType 不同
stateNode any HostComponent 对应 DOM 节点;ClassComponent 对应类实例;HostRoot 对应 FiberRoot

树链接 — 这个 fiber 在树中的位置?

字段 类型 用途
return Fiber | null 父 fiber(命名为"return",类比调用栈的返回地址)
child Fiber | null 第一个子 fiber
sibling Fiber | null 下一个兄弟 fiber
index number 在兄弟节点中的位置

输入/输出 — 流经这个 fiber 的数据是什么?

字段 类型 用途
pendingProps any 当前渲染的 props
memoizedProps any 上次完成渲染时的 props
memoizedState any 上次完成渲染时的 state(对于 hooks:hook 链表的头节点)
updateQueue mixed 待处理的 state 更新队列

调度与副作用 — React 应该何时、如何处理这个 fiber?

字段 类型 用途
lanes Lanes 待处理工作的优先级位掩码
childLanes Lanes 子树中待处理的工作
flags Flags 当前 fiber 上的副作用标志
subtreeFlags Flags 子树中副作用标志的聚合
deletions Array<Fiber> | null 待删除的子节点
alternate Fiber | null 该 fiber 在另一棵树中的对应版本(双缓冲)

树结构 — Child、Sibling、Return

React 不用数组存储子节点,而是使用单向链表:fiber 的 child 指向它的第一个子节点,每个子节点的 sibling 指向下一个兄弟节点,每个 fiber 的 return 则指回父节点。

以这段 JSX 为例:

<App>
  <Header />
  <Main />
  <Footer />
</App>

对应的 fiber 树结构如下:

graph TD
    App["App<br/>tag: FunctionComponent"]
    Header["Header<br/>tag: FunctionComponent"]
    Main["Main<br/>tag: FunctionComponent"]
    Footer["Footer<br/>tag: FunctionComponent"]

    App -->|child| Header
    Header -->|sibling| Main
    Main -->|sibling| Footer
    Header -->|return| App
    Main -->|return| App
    Footer -->|return| App

这种链表设计让工作循环(第 3 篇详述)可以在不递归、不分配数组的情况下遍历整棵树。遍历算法是:先走到 child,处理它,然后沿 sibling 链依次处理,直到没有更多兄弟节点,再通过 return 回到父节点,继续处理父节点的 sibling

FiberNode 构造函数会将这些链接初始化为 null

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

提示: 该字段叫 return 而非 parent,是因为它对应调用栈的隐喻。处理一个 fiber 就像调用一个函数,完成后你会"返回"给调用者。一旦内化这个比喻,工作循环的行为就会变得直观易懂。

双缓冲 — Current 与 WorkInProgress

React 同时维护树的两个版本current 树是当前已提交到屏幕的版本,workInProgress 树是渲染过程中正在构建的版本。

每个 fiber 都有一个 alternate 指针,指向另一棵树中的对应节点。createWorkInProgress 函数会复用已有的 alternate,或者创建一个新的 fiber:

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    // ... set up alternate links
  } else {
    // Reuse the existing alternate, reset its fields
    workInProgress.pendingProps = pendingProps;
    // ...
  }
}
sequenceDiagram
    participant C as Current Tree
    participant W as WorkInProgress Tree
    participant S as Screen

    Note over C,S: Initial state: current is committed
    C->>W: createWorkInProgress (clone fibers)
    Note over W: Render phase: modify WIP tree
    W->>C: commitRoot: swap current pointer
    Note over C,S: WIP becomes the new current
    C->>S: DOM mutations applied

双缓冲技术正是并发渲染得以实现的基础。current 树保持稳定(可被事件系统、DevTools 等读取),而 workInProgress 树在同步被修改。提交时,React 只需更新 root.current,将指针切换到新树即可。

WorkTag — 30 种 Fiber 类型

每个 fiber 的 tag 字段是一个数字常量,定义在 ReactWorkTags.js 中,用于标识该 fiber 代表哪种组件。beginWork 函数(第 3 篇详述)会通过一个大型 switch 语句按 tag 分发到对应的处理逻辑。

以下是按类别分组的 tag 列表:

Tag 类别
FunctionComponent 0 基础组件
ClassComponent 1 基础组件
HostRoot 3 宿主元素
HostPortal 4 宿主元素
HostComponent 5 宿主元素(如 <div>
HostText 6 宿主元素
HostHoistable 26 宿主元素(如 <link><script>
HostSingleton 27 宿主元素(如 <html><head><body>
Fragment 7 结构类
Mode 8 结构类
ContextProvider 10 Context
ContextConsumer 9 Context
SuspenseComponent 13 边界
SuspenseListComponent 19 边界
ActivityComponent 31 边界(前身为 Offscreen)
MemoComponent 14 优化
SimpleMemoComponent 15 优化(使用默认比较的函数组件)
LazyComponent 16 优化
ForwardRef 11 包装器
Profiler 12 开发工具
ViewTransitionComponent 30 过渡动画
Throw 29 错误处理

注意值 2 被跳过(原 IndeterminateComponent,已在 React 19 中移除),值 20 同样缺失。

副作用 Flags 与阶段掩码

每个 fiber 的 flags 字段是一个 31 位的位掩码,定义在 ReactFiberFlags.js 中,用于告知提交阶段需要执行哪些副作用。

flowchart TD
    subgraph DF["Dynamic Flags"]
        P["Placement (0b10)<br/>Insert into DOM"]
        U["Update (0b100)<br/>Apply prop changes"]
        CD["ChildDeletion (0b10000)<br/>Remove children"]
        R["Ref (0b1000000000)<br/>Attach/detach refs"]
        PA["Passive (0b100000000000)<br/>useEffect"]
        V["Visibility (0b10000000000000)<br/>Show/hide"]
    end

    subgraph PhasM["Phase Masks"]
        BM["BeforeMutationMask<br/>Snapshot"]
        MM["MutationMask<br/>Placement | Update | ChildDeletion<br/>| Ref | Hydrating | Visibility"]
        LM["LayoutMask<br/>Update | Callback | Ref | Visibility"]
        PM["PassiveMask<br/>Passive | Visibility | ChildDeletion"]
    end

    DF --> PhasM

阶段掩码是关键的优化手段。提交阶段,React 会对树进行多次遍历(before-mutation、mutation、layout、passive)。每个子阶段都有一个掩码,定义了它关心哪些 flags。subtreeFlags 字段聚合了整个子树的 flags,这样 React 就能跳过那些没有相关副作用的子树。

例如,MutationMask 定义如下:

export const MutationMask =
  Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility | FormReset;

如果某个 fiber 的 subtreeFlags & MutationMask 为零,mutation 子阶段会直接跳过整个子树。

此外还有静态 flags(第 67-88 行),如 LayoutStaticPassiveStaticViewTransitionStatic。与每次渲染后都会重置的动态 flags 不同,静态 flags 在 fiber 的整个生命周期内持久存在。它们表示某个 fiber 曾经使用过某类副作用,从而在卸载时实现优化——如果 PassiveStatic 从未被设置,React 就不必遍历整个子树去查找 passive effect 的清理函数。

FiberRoot 与根节点创建

Fiber 节点代表树中的各个组件,而 FiberRoot 则是容器层级的数据结构,持有整棵树的调度与生命周期状态。

FiberRoot 由 FiberRootNode 创建,包含以下字段:

  • current:已提交的根 fiber(一个 HostRoot fiber)
  • containerInfo:DOM 容器元素
  • pendingLanessuspendedLanespingedLanesexpiredLanes:用于调度的 lane 追踪
  • callbackNodecallbackPriority:与调度器的集成
  • next:链接指针,将有待处理工作的根节点串成链表
  • finishedWork:已完成、等待提交的 workInProgress 树

FiberRoot 与 HostRoot fiber 之间是双向引用关系:

graph LR
    FR["FiberRoot<br/>(container-level state)"]
    HR["HostRoot Fiber<br/>(tag: 3)"]
    FR -->|"current"| HR
    HR -->|"stateNode"| FR
    HR -->|"child"| App["App Fiber"]

当你调用 createRoot(container) 时,DOM renderer 会通过 createFiberRoot 创建 FiberRoot 及其初始 HostRoot fiber。HostRoot fiber 比较特殊——它始终是树中最顶层的 fiber,是渲染的"入口点"fiber。

Mode 位

每个 fiber 还携带一个 mode 字段,这是一个小型位掩码,定义在 ReactTypeOfMode.js 中:

export const NoMode =            0b0000000;
export const ConcurrentMode =    0b0000001;
export const ProfileMode =       0b0000010;
export const StrictLegacyMode =  0b0001000;
export const StrictEffectsMode = 0b0010000;

Mode 位从父节点继承到子节点,创建后不再改变。ConcurrentMode 开启时间切片;ProfileMode 开启 Profiler 的计时功能;StrictEffectsMode 在开发环境下会双重调用副作用。

提示: 调试 React 内部时,fiber 上最有价值的字段是:tag(节点类型)、memoizedState(当前 state 或 hook 链表)、flags(待处理的副作用)以及 lanes(已调度工作的优先级)。

下一篇

现在你已经理解了 Fiber 数据结构——它的树形结构、双缓冲生命周期、类型 tag 以及 flag 系统。接下来,让我们看它如何运转起来。下一篇文章将追踪工作循环:React 的核心引擎,沿树向下调用 beginWork,向上调用 completeWork,最终将结果提交到屏幕。