Skip to content

Fiber 数据结构

背景

在 React 16 之前,React 的协调(Reconciliation)过程是同步的,这意味着一旦更新开始,它就会一口气执行完毕,直到 DOM 更新完成。这种“一气呵成”的模式在处理大型复杂应用时,可能导致主线程长时间被占用,从而造成页面卡顿、响应不及时,影响用户体验。为了解决这个问题,React 团队引入了 Fiber 架构,其核心目标是实现可中断、可恢复的增量渲染。

Fiber 不仅仅是一个数据结构,它也是 React 新的协调算法(Reconciliation Algorithm)的核心工作单元和调度单位。 每个 Fiber 节点代表一个工作单元。

Fiber 架构的引入,使得 React 能够:

  • 可中断与恢复: 将协调过程拆分成小单元,可以在浏览器空闲时执行,或者在有更高优先级任务(如用户输入、动画)时暂停,待空闲时再恢复。
  • 优先级调度: 允许为不同类型的更新设置不同的优先级,确保高优先级更新(如用户交互)能够及时响应。
  • 并发模式: 为未来的并发特性(Concurrent Mode)和 Suspense 奠定了基础,使得 React 应用能够更好地利用多核 CPU 和异步操作。

属性

每个 Fiber 节点都是一个 JavaScript 对象,包含了大量用于描述组件状态和协调过程的信息。以下是 Fiber 节点中一些关键属性的详细解释:

javascript
// react/packages/react-reconciler/src/ReactFiber.js
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 实例
  this.tag = tag; // Fiber 的类型,如 FunctionComponent, ClassComponent, HostRoot 等
  this.key = key; // React Element 上的 key 属性,用于列表渲染时的优化
  this.elementType = null; // React Element 的 type,如函数组件本身,类组件的 class,或者 'div' 字符串
  this.type = null; // 与 elementType 类似,但对于 HostComponent,它就是 DOM 元素的标签名
  this.stateNode = null; // Fiber 对应的真实 DOM 节点(对于 HostComponent)或组件实例(对于 ClassComponent)

  // Fiber
  this.return = null; // 指向父 Fiber 节点
  this.child = null; // 指向第一个子 Fiber 节点
  this.sibling = null; // 指向下一个兄弟 Fiber 节点
  this.index = 0; // 在兄弟节点中的索引

  this.ref = null; // React Element 上的 ref 属性

  this.pendingProps = pendingProps; // 新的 props,等待处理
  this.memoizedProps = null; // 上一次渲染时使用的 props,用于比较是否需要更新
  this.updateQueue = null; // 存储状态更新(setState)、回调函数等
  this.memoizedState = null; // 上一次渲染时使用的 state,用于比较是否需要更新
  this.dependencies = null; // 存储 Hooks 的依赖项,如 useContext, useMutableSource

  this.mode = mode; // Fiber 的模式,如 ConcurrentMode, BlockingMode, NoMode

  // 副作用
  this.flags = NoFlags; // 标记 Fiber 节点需要执行的副作用(如 Placement, Update, Deletion)
  this.subtreeFlags = NoFlags; // 子树中所有 Fiber 节点需要执行的副作用的集合
  this.deletions = null; // 存储需要删除的子 Fiber 节点

  // 调度
  this.lanes = NoLanes; // 标记该 Fiber 节点及其子树中待处理的更新的优先级
  this.childLanes = NoLanes; // 子树中所有 Fiber 节点待处理的更新的优先级集合

  // 指向另一个 Fiber 树中的对应节点(current 树指向 work-in-progress 树,反之亦然)
  this.alternate = null;

  // 调试信息 (仅在 __DEV__ 模式下)
  if (__DEV__) {
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
    this.actualStartTime = -1;
    this.lastBaseUpdate = null;
    this.firstBaseUpdate = null;
    this.expirationTime = NoTimestamp;
    this.memoizedProps = null;
    this.pendingProps = null;
    this.nextEffect = null;
    this.firstEffect = null;
    this.lastEffect = null;
    this.parent = null;
    this.memoizedState = null;
    this.dependencies = null;
  }
}

了解Fiber节点的属性是理解 Fiber 运作的基础。你需要熟悉一些关键属性别。

基本属性

  • tag:表示 Fiber 节点的类型,例如 HostRoot(应用根节点)、FunctionComponent(函数组件)、ClassComponent(类组件)、HostComponent(原生 DOM 元素,如 divspan)、HostText(文本节点)等。React 根据 tag 的值来决定如何处理该 Fiber 节点。
  • key:与 React Element 上的 key 属性相同,用于在列表渲染时帮助 React 识别哪些子元素发生了变化,从而优化更新。
  • elementTypetypeelementType 通常指 React Element 的 type 属性,例如函数组件的函数本身,类组件的类本身,或者原生 DOM 元素的字符串标签名(如 'div')。type 在某些情况下与 elementType 相同,但在 HostComponent 中,type 存储的是 DOM 元素的标签名。
  • stateNode:指向与 Fiber 节点关联的实际实例。对于 HostComponent,它指向真实的 DOM 节点;对于 ClassComponent,它指向组件的实例;对于 FunctionComponent,它通常为 null

树结构属性

  • returnchildsibling:这三个属性构成了 Fiber 树的骨架,它们是 Fiber 节点之间关系的指针,形成了一个单向链表和树状结构。return 指向父节点,child 指向第一个子节点,sibling 指向下一个兄弟节点。
  • index: 在兄弟节点中的索引

状态和属性

  • pendingPropsmemoizedPropspendingProps 存储了新的、待处理的 props。memoizedProps 存储了上一次成功渲染时使用的 props。React 通过比较这两个属性来判断组件的 props 是否发生变化,从而决定是否需要重新渲染。
  • updateQueue:一个链表结构,存储了该 Fiber 节点上待处理的更新,例如 setState 调用、forceUpdate 调用等。在协调阶段,React 会遍历 updateQueue 来计算新的 state
  • memoizedState:存储了上一次成功渲染时使用的 state。对于函数组件,它存储了 Hooks 的状态(如 useState 的值);对于类组件,它存储了组件的 state
  • dependencies: 存储了 Hooks 的依赖项,其是 React Fiber 架构中实现高效 Context 更新传播的关键部分。它通过一个链表结构记录了 Fiber 节点对各个 Context 的依赖情况,使得 React 能够在 Context 值变化时,精确且快速地找到需要更新的组件,从而优化了性能并保证了状态的一致性。

副作用

  • flagssubtreeFlags:这是 Fiber 架构中非常重要的属性,用于标记 Fiber 节点需要执行的副作用(Side Effect)。例如,Placement 表示需要插入 DOM 节点,Update 表示需要更新 DOM 节点属性,Deletion 表示需要删除 DOM 节点。subtreeFlags 是其子树中所有 flags 的集合,用于快速判断子树中是否存在副作用,避免不必要的遍历。
  • deletions: 需要删除的 Fiber 数组。

调度属性

  • laneschildLanes:在 React 18+ 中,lanes 替代了 expirationTimesuspensePriority,用于表示更新的优先级。它是一个位掩码,不同的位代表不同的优先级。lanes 标记了当前 Fiber 节点上待处理的更新的优先级,childLanes 标记了其子树中所有 Fiber 节点待处理的更新的优先级集合。这个章节将在后文详细的讲解
  • alternate:指向另一个 Fiber 树中对应的 Fiber 节点。例如,如果当前 Fiber 节点属于 Current Fiber Tree,那么 alternate 就指向 Work-in-Progress Fiber Tree 中对应的节点,反之亦然。这个属性是实现“双缓冲”机制的关键。

工作标签系统(workTag)

React Fiber 的“工作标签系统”主要是指 Fiber 节点上的 tag 属性。这个 tag 是一个数字枚举值 (在源码中通常定义为 WorkTag 枚举),它用来标识一个 Fiber 节点代表的是什么类型的工作单元或组件类型。不同的 tag 会导致 React Reconciler (协调器) 在 beginWorkcompleteWork 阶段对该 Fiber 节点采取不同的处理逻辑。

tag 属性是 Fiber 节点的核心属性之一,它决定了:

  • 如何处理该 Fiber 节点: 例如,是调用函数组件、实例化类组件、创建 DOM 元素,还是处理 Context、Suspense 等特殊逻辑。
  • 该 Fiber 节点可以拥有哪些子节点: 例如,HostComponent (DOM 元素) 的子节点通常也是 HostComponent 或 HostText。
  • 在 Commit 阶段需要执行哪些副作用: 例如,HostComponent 可能需要进行 DOM 操作。

常见的 Fiber tag (WorkTag) 类型及其含义:

以下是一些在 React 源码中常见的 WorkTag 值:

  • FunctionComponent (0): 代表一个函数组件。在 beginWork 中会直接调用该函数获取其子元素。
  • ClassComponent (1): 代表一个类组件。在 beginWork 中会实例化组件 (如果需要),调用生命周期方法 (如 render) 获取子元素。
  • IndeterminateComponent (2): 初始状态,当 React 还不能确定一个组件是函数组件还是类组件时使用 (例如,一个函数返回了另一个函数)。在第一次渲染后会解析成 FunctionComponentClassComponent
  • HostRoot (3): Fiber 树的根节点,通常是调用 ReactDOM.createRoot(container).render(<App />) 时创建的,container DOM 元素会关联到这个 Fiber。
  • HostPortal (4): 代表一个 Portal,允许将子节点渲染到父组件 DOM 层级之外的 DOM 节点中。
  • HostComponent (5): 代表一个原生的 DOM 元素 (如 <div>, <span>, <p> 等)。beginWork 会处理其 childrencompleteWork 负责创建或更新真实的 DOM 节点。
  • HostText (6): 代表一个文本节点 (DOM 中的 TextNode)。它没有子节点。
  • Fragment (7): 代表 <React.Fragment><> 语法糖。它本身不渲染到 DOM,只是用来包裹一组子元素。
  • Mode (8): 代表 React 的模式组件,如 <React.StrictMode><React.ConcurrentMode> (虽然 ConcurrentMode 后来更多是通过并发特性开关来控制)。它们会影响其子树中 Fiber 的行为。
  • ContextConsumer (9): 代表使用 Context.Consumer 的组件或 useContext Hook 所在的组件,用于订阅 Context 的变化。
  • ContextProvider (10): 代表 Context.Provider 组件,用于向下传递 Context 值。
  • ForwardRef (11): 代表通过 React.forwardRef 创建的组件,允许父组件获取子组件内部的 DOM 节点或组件实例的 ref。
  • Profiler (12): 代表 <React.Profiler> 组件,用于性能分析,测量渲染时间和提交次数。
  • SuspenseComponent (13): 代表 <React.Suspense> 组件,用于处理代码分割和异步数据加载的 “加载中” UI 状态。
  • MemoComponent (14): 代表通过 React.memo 优化的组件。beginWork 会进行 props 的浅比较来决定是否跳过渲染。
  • SimpleMemoComponent (15): MemoComponent 的一种特定形式,当比较函数简单时使用。
  • LazyComponent (16): 代表通过 React.lazy() 创建的动态加载组件。beginWork 会处理其加载状态。
  • ScopeComponent (17): 一个实验性的特性,用于创建隔离的事件作用域。
  • OffscreenComponent (18): 代表 <React.Offscreen> (实验性),用于控制组件的可见性和渲染行为,例如在屏幕外预渲染或保持状态隐藏。
  • LegacyHiddenComponent (19): 旧版的隐藏组件,类似 OffscreenComponent 但行为有所不同。
  • CacheComponent (20): (实验性) 与 React Cache 相关,用于缓存数据获取结果。
  • TracingMarkerComponent (21): (实验性) 与 React 的内部追踪和 DevTools 相关。
  • HostHoistable (22): (实验性/内部使用) 与静态提升优化相关,可能用于标记那些可以在构建时提升的静态子树或元素。
  • HostSingleton (23): (实验性/内部使用) 可能与确保某些类型的 HostComponent (如 <html>, <head>, <body>) 在文档中是单例的逻辑相关。

工作流程中的作用:

createFiberFromTypeAndProps 函数中,React 会根据传入的 type (组件构造函数、字符串标签名、或 React 内部类型如 REACT_FRAGMENT_TYPE) 来决定新创建的 Fiber 节点的 tag

然后,在 beginWork 函数中,会有一个大的 switch (workInProgress.tag) 语句,根据不同的 tag 执行不同的协调逻辑:

javascript
// 伪代码示例
function beginWork(current, workInProgress, lanes) {
  // ... bailout 逻辑 ...

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      return mountIndeterminateComponent(current, workInProgress, workInProgress.type, lanes);
    case LazyComponent:
      return updateLazyComponent(current, workInProgress, lanes);
    case FunctionComponent:
      return updateFunctionComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, lanes);
    case ClassComponent:
      return updateClassComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, lanes);
    case HostRoot:
      return updateHostRoot(current, workInProgress, lanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, lanes);
    case HostText:
      return null; // HostText 没有子节点
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, lanes);
    // ... 其他 case
  }
}

同样,completeWork 函数中也会根据 tag 来执行不同的收尾工作,比如创建 DOM 实例、准备 DOM 更新、收集 effect 等。

因此,WorkTag 是 React Fiber 架构中区分不同工作类型、指导协调过程和实现各种 React 特性的关键机制。

副作用标志系统

React Fiber 的副作用标志系统 (Flags / Effect Tags) 是其协调和提交机制的核心部分。每个 Fiber 节点都有一个 flags (在旧版本或某些上下文中也可能被称为 effectTag) 属性,它是一个位掩码 (bitmask),用来标记该 Fiber 节点在 Commit 阶段需要执行哪些类型的副作用操作。

这些标志是在 Render 阶段的 beginWorkcompleteWork 过程中被设置的。当 workInProgress 树构建完成后,React 会遍历这棵树,收集所有带有副作用标志的 Fiber 节点,并在 Commit 阶段按照特定顺序执行这些操作。

我们可以将这些标志大致分为以下几类。请注意,具体的标志名称和值可能随 React 版本演进,以下基于常见的 Fiber Flags 定义进行解释

基础标志 (DOM 操作相关)

这些标志通常指示对 DOM 树的直接修改。

  • Placement (或 Update 中包含的插入逻辑):
    • 含义: 表示该 Fiber 节点对应的 DOM 元素需要被插入到 DOM 树中。这通常发生在组件首次渲染,或者一个之前未渲染的组件现在需要被渲染时。
    • 设置时机:reconcileChildren 过程中,当发现一个新的子节点时。
  • Update:
    • 含义: 表示该 Fiber 节点对应的 DOM 元素或组件的属性 (props)、状态 (state) 或上下文 (context) 发生了变化,需要更新。对于 HostComponent,这可能意味着需要更新 DOM 属性、样式或事件监听器。对于 ClassComponent,可能需要调用 componentDidUpdate
    • 设置时机: 当 Diff 算法检测到 props 或 state 变化时。
  • Deletion (或 ChildDeletion):
    • 含义: 表示该 Fiber 节点及其子树需要从 DOM 中移除,并且需要执行相应的清理工作 (如调用 componentWillUnmount,解绑 ref,移除事件监听器等)。
    • 设置时机:reconcileChildren 发现某个旧的子节点在新子节点列表中不存在时。
  • Hydrating:
    • 含义: 表示该 Fiber 节点正在进行服务端渲染 (SSR) 的注水 (hydration) 过程。它会尝试附加到现有的 DOM 结构上,而不是创建新的 DOM 节点。
    • 设置时机:hydrate 模式下,当协调 HostComponent 时。

生命周期标志

这些标志主要与类组件的生命周期方法和 Hooks 的 effect 相关。

  • LifecycleEffectMask (这是一个掩码,包含了多种生命周期相关的 flags):
    • Update (也与生命周期相关): 如上所述,对于类组件,Update 标志也可能触发 componentDidUpdate
    • LayoutMask (或 Layout, LayoutStatic):
      • 含义: 标记需要执行 useLayoutEffect Hook 的回调函数,或者类组件的 componentDidMountcomponentDidUpdate 方法。这些 effect 是在 DOM 更新完成后、浏览器绘制之前同步执行的。
      • 设置时机: 当组件挂载或更新,并且定义了相应的 layout effect 时。
    • PassiveMask (或 Passive, PassiveStatic):
      • 含义: 标记需要执行 useEffect Hook 的回调函数。这些 effect 是在浏览器绘制完成后异步执行的,不会阻塞浏览器渲染。
      • 设置时机: 当组件挂载或更新,并且定义了相应的 passive effect 时。
    • Callback:
      • 含义: 表示 Fiber 节点有回调函数需要执行,例如 setState 的回调参数。
      • 设置时机:setState 带有回调时。
    • Ref:
      • 含义: 表示该 Fiber 节点的 ref 需要被更新 (附加新的 ref 或分离旧的 ref)。
      • 设置时机: 当组件挂载、卸载或 ref 对象/回调发生变化时。
    • Snapshot:
      • 含义: 对于类组件,标记需要在 DOM 更新前调用 getSnapshotBeforeUpdate 生命周期方法。
      • 设置时机: 当类组件定义了 getSnapshotBeforeUpdate 并且即将更新时。

三、其他特定功能标志

  • Visibility:
    • 含义:<OffscreenComponent> 或类似特性相关,标记组件的可见性发生了变化,可能需要隐藏或显示 DOM 子树,并触发生存周期或 effect。
  • DidCapture:
    • 含义: 表示该 Fiber 节点 (通常是一个错误边界) 捕获了其子树中抛出的错误。
    • 设置时机: 当错误边界捕获到错误时。
  • ShouldCapture:
    • 含义: 在 Render 阶段,如果一个组件抛出错误,其父级错误边界会被标记上 ShouldCapture,表明它应该尝试捕获这个错误。
  • PerformedWork:
    • 含义: 一个内部标志,表示这个 Fiber 节点在当前的 Render 阶段至少执行了一些工作,即使它最终可能被 bailout (跳过)。这有助于 React 决定是否可以安全地重用上一次的输出。
    • 设置时机:beginWork 中,如果 Fiber 没有被完全跳过。

标志掩码

标志掩码是一些预定义的常量,它们组合了多个相关的副作用标志,用于方便地检查一组特定的副作用是否存在,或者用于在 Fiber 树中传播副作用。

  • NoFlags (或 NoEffects):
    • 值: 0b0000000000000000000000000000000 (即 0)
    • 含义: 表示该 Fiber 节点没有任何副作用需要执行。
  • HostEffectMask:
    • 含义: 这是一个掩码,通常包含了所有可能影响宿主环境 (如 DOM) 的副作用,例如 Placement, Update, Deletion, Ref, Hydrating, Visibility 等。
  • LifecycleEffectMask (如上所述):
    • 含义: 包含了与组件生命周期和 Hooks (Layout, Passive) 相关的副作用。
  • MutationMask (或 HostMutationMask):
    • 含义: 包含了所有会导致 DOM 树结构发生变更的副作用,主要是 Placement, Update, Deletion, HostText 的内容更新。
  • LayoutMask (或 HostLayoutMask):
    • 含义: 包含了所有需要在 Layout 阶段执行的副作用,主要是 Update (对于 componentDidMount/Update) 和 Ref
  • PassiveMask (或 HostPassiveMask):
    • 含义: 包含了所有需要在 Passive 阶段执行的副作用。
  • PerformedWork (本身也是一个标志,但有时也用于掩码检查)

使用方式:

React 使用位运算来设置、检查和清除这些标志。

  • 设置标志: fiber.flags |= Placement; (使用按位或 |=)
  • 检查标志: if (fiber.flags & Update) { ... } (使用按位与 &)
  • 清除标志: fiber.flags &= ~Placement; (使用按位与和按位非 ~)

completeWork 阶段,子节点的 flags 会被合并到父节点的 subtreeFlags 属性上。这样,在 Commit 阶段开始时,React 只需要检查根 Fiber 的 subtreeFlags 就能快速知道整棵树中是否有某种类型的副作用,从而优化遍历。


微信公众号二维码