Appearance
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 元素,如div、span)、HostText(文本节点)等。React 根据tag的值来决定如何处理该 Fiber 节点。key:与 React Element 上的key属性相同,用于在列表渲染时帮助 React 识别哪些子元素发生了变化,从而优化更新。elementType和type:elementType通常指 React Element 的type属性,例如函数组件的函数本身,类组件的类本身,或者原生 DOM 元素的字符串标签名(如'div')。type在某些情况下与elementType相同,但在HostComponent中,type存储的是 DOM 元素的标签名。stateNode:指向与 Fiber 节点关联的实际实例。对于HostComponent,它指向真实的 DOM 节点;对于ClassComponent,它指向组件的实例;对于FunctionComponent,它通常为null。
树结构属性
return、child、sibling:这三个属性构成了 Fiber 树的骨架,它们是 Fiber 节点之间关系的指针,形成了一个单向链表和树状结构。return指向父节点,child指向第一个子节点,sibling指向下一个兄弟节点。index: 在兄弟节点中的索引
状态和属性
pendingProps和memoizedProps:pendingProps存储了新的、待处理的 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 值变化时,精确且快速地找到需要更新的组件,从而优化了性能并保证了状态的一致性。
副作用
flags和subtreeFlags:这是 Fiber 架构中非常重要的属性,用于标记 Fiber 节点需要执行的副作用(Side Effect)。例如,Placement表示需要插入 DOM 节点,Update表示需要更新 DOM 节点属性,Deletion表示需要删除 DOM 节点。subtreeFlags是其子树中所有flags的集合,用于快速判断子树中是否存在副作用,避免不必要的遍历。deletions: 需要删除的 Fiber 数组。
调度属性
lanes和childLanes:在 React 18+ 中,lanes替代了expirationTime和suspensePriority,用于表示更新的优先级。它是一个位掩码,不同的位代表不同的优先级。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 (协调器) 在 beginWork 和 completeWork 阶段对该 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 还不能确定一个组件是函数组件还是类组件时使用 (例如,一个函数返回了另一个函数)。在第一次渲染后会解析成FunctionComponent或ClassComponent。HostRoot(3): Fiber 树的根节点,通常是调用ReactDOM.createRoot(container).render(<App />)时创建的,containerDOM 元素会关联到这个 Fiber。HostPortal(4): 代表一个 Portal,允许将子节点渲染到父组件 DOM 层级之外的 DOM 节点中。HostComponent(5): 代表一个原生的 DOM 元素 (如<div>,<span>,<p>等)。beginWork会处理其children,completeWork负责创建或更新真实的 DOM 节点。HostText(6): 代表一个文本节点 (DOM 中的 TextNode)。它没有子节点。Fragment(7): 代表<React.Fragment>或<>语法糖。它本身不渲染到 DOM,只是用来包裹一组子元素。Mode(8): 代表 React 的模式组件,如<React.StrictMode>或<React.ConcurrentMode>(虽然 ConcurrentMode 后来更多是通过并发特性开关来控制)。它们会影响其子树中 Fiber 的行为。ContextConsumer(9): 代表使用Context.Consumer的组件或useContextHook 所在的组件,用于订阅 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 阶段的 beginWork 和 completeWork 过程中被设置的。当 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 变化时。
- 含义: 表示该 Fiber 节点对应的 DOM 元素或组件的属性 (props)、状态 (state) 或上下文 (context) 发生了变化,需要更新。对于 HostComponent,这可能意味着需要更新 DOM 属性、样式或事件监听器。对于 ClassComponent,可能需要调用
Deletion(或ChildDeletion):- 含义: 表示该 Fiber 节点及其子树需要从 DOM 中移除,并且需要执行相应的清理工作 (如调用
componentWillUnmount,解绑 ref,移除事件监听器等)。 - 设置时机: 当
reconcileChildren发现某个旧的子节点在新子节点列表中不存在时。
- 含义: 表示该 Fiber 节点及其子树需要从 DOM 中移除,并且需要执行相应的清理工作 (如调用
Hydrating:- 含义: 表示该 Fiber 节点正在进行服务端渲染 (SSR) 的注水 (hydration) 过程。它会尝试附加到现有的 DOM 结构上,而不是创建新的 DOM 节点。
- 设置时机: 在
hydrate模式下,当协调 HostComponent 时。
生命周期标志
这些标志主要与类组件的生命周期方法和 Hooks 的 effect 相关。
LifecycleEffectMask(这是一个掩码,包含了多种生命周期相关的 flags):Update(也与生命周期相关): 如上所述,对于类组件,Update标志也可能触发componentDidUpdate。LayoutMask(或Layout,LayoutStatic):- 含义: 标记需要执行
useLayoutEffectHook 的回调函数,或者类组件的componentDidMount和componentDidUpdate方法。这些 effect 是在 DOM 更新完成后、浏览器绘制之前同步执行的。 - 设置时机: 当组件挂载或更新,并且定义了相应的 layout effect 时。
- 含义: 标记需要执行
PassiveMask(或Passive,PassiveStatic):- 含义: 标记需要执行
useEffectHook 的回调函数。这些 effect 是在浏览器绘制完成后异步执行的,不会阻塞浏览器渲染。 - 设置时机: 当组件挂载或更新,并且定义了相应的 passive effect 时。
- 含义: 标记需要执行
Callback:- 含义: 表示 Fiber 节点有回调函数需要执行,例如
setState的回调参数。 - 设置时机: 当
setState带有回调时。
- 含义: 表示 Fiber 节点有回调函数需要执行,例如
Ref:- 含义: 表示该 Fiber 节点的 ref 需要被更新 (附加新的 ref 或分离旧的 ref)。
- 设置时机: 当组件挂载、卸载或 ref 对象/回调发生变化时。
Snapshot:- 含义: 对于类组件,标记需要在 DOM 更新前调用
getSnapshotBeforeUpdate生命周期方法。 - 设置时机: 当类组件定义了
getSnapshotBeforeUpdate并且即将更新时。
- 含义: 对于类组件,标记需要在 DOM 更新前调用
三、其他特定功能标志
Visibility:- 含义: 与
<OffscreenComponent>或类似特性相关,标记组件的可见性发生了变化,可能需要隐藏或显示 DOM 子树,并触发生存周期或 effect。
- 含义: 与
DidCapture:- 含义: 表示该 Fiber 节点 (通常是一个错误边界) 捕获了其子树中抛出的错误。
- 设置时机: 当错误边界捕获到错误时。
ShouldCapture:- 含义: 在 Render 阶段,如果一个组件抛出错误,其父级错误边界会被标记上
ShouldCapture,表明它应该尝试捕获这个错误。
- 含义: 在 Render 阶段,如果一个组件抛出错误,其父级错误边界会被标记上
PerformedWork:- 含义: 一个内部标志,表示这个 Fiber 节点在当前的 Render 阶段至少执行了一些工作,即使它最终可能被 bailout (跳过)。这有助于 React 决定是否可以安全地重用上一次的输出。
- 设置时机: 在
beginWork中,如果 Fiber 没有被完全跳过。
标志掩码
标志掩码是一些预定义的常量,它们组合了多个相关的副作用标志,用于方便地检查一组特定的副作用是否存在,或者用于在 Fiber 树中传播副作用。
NoFlags(或NoEffects):- 值:
0b0000000000000000000000000000000(即 0) - 含义: 表示该 Fiber 节点没有任何副作用需要执行。
- 值:
HostEffectMask:- 含义: 这是一个掩码,通常包含了所有可能影响宿主环境 (如 DOM) 的副作用,例如
Placement,Update,Deletion,Ref,Hydrating,Visibility等。
- 含义: 这是一个掩码,通常包含了所有可能影响宿主环境 (如 DOM) 的副作用,例如
LifecycleEffectMask(如上所述):- 含义: 包含了与组件生命周期和 Hooks (Layout, Passive) 相关的副作用。
MutationMask(或HostMutationMask):- 含义: 包含了所有会导致 DOM 树结构发生变更的副作用,主要是
Placement,Update,Deletion,HostText的内容更新。
- 含义: 包含了所有会导致 DOM 树结构发生变更的副作用,主要是
LayoutMask(或HostLayoutMask):- 含义: 包含了所有需要在 Layout 阶段执行的副作用,主要是
Update(对于componentDidMount/Update) 和Ref。
- 含义: 包含了所有需要在 Layout 阶段执行的副作用,主要是
PassiveMask(或HostPassiveMask):- 含义: 包含了所有需要在 Passive 阶段执行的副作用。
PerformedWork(本身也是一个标志,但有时也用于掩码检查)
使用方式:
React 使用位运算来设置、检查和清除这些标志。
- 设置标志:
fiber.flags |= Placement;(使用按位或|=) - 检查标志:
if (fiber.flags & Update) { ... }(使用按位与&) - 清除标志:
fiber.flags &= ~Placement;(使用按位与和按位非~)
在 completeWork 阶段,子节点的 flags 会被合并到父节点的 subtreeFlags 属性上。这样,在 Commit 阶段开始时,React 只需要检查根 Fiber 的 subtreeFlags 就能快速知道整棵树中是否有某种类型的副作用,从而优化遍历。
