Appearance
第 2.2 节:Fiber:新一代的协调引擎
在 React 16 之前,协调过程(Reconciliation)是同步且不可中断的。当 React 开始 diff 一棵巨大的组件树时,主线程会被长时间占用,导致页面无法响应用户输入、动画掉帧等问题。为了解决这个问题,React 团队引入了全新的协调引擎——Fiber。
Fiber 的核心思想是将一个大的更新任务拆分成许多小的工作单元(work unit)。每个工作单元完成后,React 都可以将控制权交还给浏览器主线程,去检查是否有更高优先级的任务(如用户输入)需要处理。这样,渲染过程就变成了可中断、可恢复的,从而实现了“并发渲染”(Concurrent Rendering)。
Fiber 不仅仅是一个调度算法,它也是一种新的核心数据结构,用于表示组件树的节点。
2.2.1 Fiber 节点的数据结构
每个 ReactElement 在协调过程中都会被转换成一个 Fiber 节点。这个节点承载了组件的类型、状态、副作用(effects)以及它在 Fiber 树中的关系(父、子、兄弟节点)。
让我们深入源码,看看一个 Fiber 节点是如何被定义的。
文件定位:packages/react-reconciler/src/ReactFiber.js
javascript
function FiberNode(
this: $FlowFixMe,
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.refCleanup = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
// ... DEV-only and Profiler fields
}可以看到,一个 Fiber 节点就是一个普通的 JavaScript 对象,其属性可以分为几类:
实例相关(Instance)
tag:WorkTag枚举值,定义了 Fiber 节点的类型。例如,FunctionComponent、ClassComponent、HostComponent(DOM 元素) 等。这是 React 判断如何处理一个节点的基础。key: 我们在写列表时常用的key属性,用于在 diff 过程中识别节点。type: 组件的类型。对于函数组件,它是函数本身;对于类组件,它是 class;对于 DOM 元素,它是字符串(如'div')。stateNode: 指向组件实例的指针。对于类组件,它是组件的实例;对于 DOM 元素,它是真实的 DOM 节点;对于函数组件,它为null。
树结构关系(Fiber)
return: 指向父 Fiber 节点。child: 指向第一个子 Fiber 节点。sibling: 指向下一个兄弟 Fiber 节点。
这三个属性构成了 Fiber 树的核心链表结构。React 不再使用递归遍历,而是通过这套指针在内存中构建和遍历树,使得遍历过程可以随时中断和恢复。
状态与 Props
pendingProps: 从ReactElement传来的,即将要处理的 props。memoizedProps: 上一次渲染完成时使用的 props。通过比较pendingProps和memoizedProps,React 可以判断 props 是否发生变化。memoizedState: 对于类组件,是 state;对于函数组件,是 Hooks 构成的链表。updateQueue: 一个队列,存储着待处理的状态更新(setState)或副作用(useEffect)。
副作用(Effects)
flags: 也称为effectTag,一个位掩码,用于标记此 Fiber 节点需要执行的副作用类型,如Placement(插入)、Update(更新)、Deletion(删除) 等。subtreeFlags: 其所有子树中包含的flags的集合。这是一种优化,如果subtreeFlags为NoFlags,React 就可以跳过对整个子树的遍历来寻找副作用。deletions: 一个数组,存储着需要被删除的子 Fiber 节点。
调度相关(Scheduling)
lanes: 一个位掩码,表示此 Fiber 节点上待处理的更新所属的优先级。childLanes: 子节点中待处理的更新的优先级。alternate: Fiber 架构的精髓所在。它指向另一个 Fiber 节点,即当前节点在 work-in-progress 树中的对应节点。我们将在下一节详细探讨。
值得注意的是,React 19 源码中还包含一个名为 enableObjectFiber 的特性开关。当它开启时,会使用一个普通对象字面量 createFiberImplObject 来创建 Fiber,而不是 new FiberNode()。这主要是为了在某些 JavaScript 引擎(如 Hermes)上获得更好的字节码优化。
javascript
// packages/react-reconciler/src/ReactFiber.js
const createFiber = enableObjectFiber
? createFiberImplObject
: createFiberImplClass;2.2.2 Fiber 树与 work-in-progress 树
为了实现可中断的渲染,React 在内存中同时维护两棵 Fiber 树。
- current 树 (当前树):这棵树对应着当前屏幕上已经渲染的 UI。它是稳定的,是用户所能看到的界面的“快照”。
- work-in-progress 树 (WIP 树):这是一棵正在后台构建的树。所有的更新都发生在这棵树上。当协调过程开始时,React 会根据
current树创建一个它的副本,即 WIP 树。
这种“双缓冲”(double buffering)技术允许 React 在不影响当前 UI 的情况下,在后台“悄悄地”完成所有计算和 diff。当 WIP 树构建完成并准备好提交时,React 只需将一个指针从 current 树切换到 WIP 树,WIP 树就成为了新的 current 树。这个切换过程是原子性的,瞬间完成,避免了页面出现不完整的 UI 状态。
alternate 属性就是连接这两棵树的桥梁。current 树中节点的 alternate 指向 WIP 树中的对应节点,反之亦然。
createWorkInProgress 函数完美地诠释了这一过程:
文件定位:packages/react-reconciler/src/ReactFiber.js
javascript
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 如果没有备用节点(通常在首次渲染时),则创建一个新的 Fiber 节点
// 并建立 current 和 workInProgress 之间的 alternate 链接
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// 如果备用节点已存在,则重用它,并重置一些状态
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
// 重置副作用标记,准备进行新一轮的计算
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
}
// 从 current 树继承 lanes、props、state 等信息
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// ...
return workInProgress;
}设计思想解读:
- 复用与性能:通过复用
alternate节点,React 最大限度地减少了内存分配和垃圾回收的开销。一个 Fiber 节点在它的生命周期中只会在current和WIP两个角色之间切换。 - 原子性与一致性:只有当整棵 WIP 树都构建完毕后,才会进行一次性的提交(Commit)。这保证了用户永远不会看到渲染到一半的、不一致的 UI。
- 可中断与恢复:如果在构建 WIP 树的过程中有更高优先级的任务进来,React 可以安全地暂停当前工作,甚至丢弃整棵 WIP 树,而不会对用户界面产生任何影响。当它恢复时,可以从
current树重新开始。
通过 Fiber 节点精巧的数据结构和双缓冲的 WIP 树机制,React 将一个宏大的渲染任务分解为可管理、可调度的工作单元,为并发渲染、时间切片等高级特性奠定了坚实的基础。