通俗易懂的React原理(二):构建Fiber tree的流程

代码以React v19.1.0为例,https://github.com/facebook/react/tree/v19.1.0

今天开始我们开始研究React的源码。我们先关注构建Fiber tree相关的内容。

这篇要涉及到beginWork和completeWork的过程,我认为其他的教程此处讲的都比较简略,重点放在了两个方法的内部,而不是两个方法是怎么被调用的。大多数教程说,beginWork是“递”的过程,而“completeWork”是归的过程。然而二者并不是简单的先递后归,而是会交替进行。而我为了让大家能尽量抓住重点,从而快速对构建Fiber的流程有一个认知,可能不会遇到一个方法,就去详细讲它。希望我的这篇文章能对读者有所帮助

workLoopConcurrentByScheduler/workLoopConcurrent/workLoopSync

多的不说,我们直接从workLoopConcurrentByScheduler/workLoopConcurrent/workLoopSync开始看,这三个就是去循环遍历Fiber tree,构建Fiber节点的方法。至于哪里调用的它们,我们先不管,都讲到scheduler的时候再来看吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}

function workLoopConcurrent(nonIdle: boolean) {
if (workInProgress !== null) {
const yieldAfter = now() + (nonIdle ? 25 : 5);
do {
// $FlowFixMe[incompatible-call] flow doesn't know that now() is side-effect free
performUnitOfWork(workInProgress);
} while (workInProgress !== null && now() < yieldAfter);
}
}

function workLoopConcurrentByScheduler() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}

可以看到,三个方法其实区别不大,都是去循环执行performUnitOfWork(workInProgress)。workInProgress在我们讲的时候,可以指正在被构建的那棵Fiber tree。但是在源码里面,更多的时候,它特指workInProgress树上面当前正遍历到的那个节点。它是一个全局变量,如果这个节点遍历完了,那么就会把它赋值成刚才节点的child之类的。

workInProgress !== null其实就是判断当前这棵树有没有构建完毕,为null就表示构建完了,结束循环。

三个方法的区别就是,workLoopConcurrent和workLoopConcurrentByScheduler每次循环的时候,会判断一下当前是否到了预设的时间,到了就打断,等下一次再被调度,继续循环。就跟我们上一篇举例的那道面试题很像。这里workInProgress这个全局变量就记录了中断的时候,我们遍历到哪个节点。

workLoopConcurrentByScheduler里面的shouldYield,来自scheduler这个包,功能可以简单理解为经过了5ms就返回true。

至于workLoopConcurrent里面的noIdle,是React最近新加的代码。原本只有sync和concurrent(现在被改叫workLoopConcurrentByScheduler)两种逻辑的,即固定不打断和每5ms打断一次。

而React在尝试一种新逻辑,它觉得原来每次5ms打断一次太频繁了,于是在大部分场景下延长了时间,就是现在的workLoopConcurrent逻辑。大部分场景下,React会给自己留25ms的时间去循环。也就是注释里说的,浏览器的主线程有些工作太频繁了(例如动画),可能让React的执行在调度中被饿死,所以给自己多留点时间,大不了就让浏览器帧数低一点。而在没那么紧急的场景下,React将自己每次循环的时间上限缩短到5ms,让浏览器能渲染更多帧。

performUnitOfWork

从上面我们就更可以看出,performUnitOfWork是构建Fiber tree的核心了,循环去执行这个方法嘛。从名字上来看,他就是执行了一个工作单元。这么说起来还是比较抽象,我们看下它源码的主要部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function performUnitOfWork(unitOfWork: Fiber): void {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = unitOfWork.alternate;

let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
// 省略,和React DevTools里面的Profiler相关。
// Profiler会记录组件渲染了多少次,每次渲染的耗时,和引起重新渲染的原因等等,对排查性能问题很有帮助
// 但是生产build不会开启这个功能
} else {
if (__DEV__) {
// 开发环境,省略不看
} else {
next = beginWork(current, unitOfWork, entangledRenderLanes);
}
}

unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}

我们对于源码抓大放小,从总体上把握流程,而不是拘泥于每个分支。因此我们主要看生产环境下会走到的代码,以及影响流程的部分。精简一下,代码变得更简单

1
2
3
4
5
6
7
8
9
10
11
12
13
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;

let next;
next = beginWork(current, unitOfWork, entangledRenderLanes);

unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}

我们利用精简后的代码,结合上一篇中的图,去体会一下上一篇理论知识中Fiber tree遍历的过程

我们去梳理一下代码的意义。假设performUnitOfWork传入的Fiber node是A,然后beginWork会通过A和A.alternate(即current树中,A对应的节点),构建一个A的子节点(next)返回。对图中而言,next就是B。子节点是存在的,那么将workInProgress赋值为B,这一次循环结束,下一次循环会执行performUnitOfWork(B)

也就是会先执行图中的1和2。然后当performUnitOfWork(C)时,next是null了,因为C没有子节点了。这个时候,会执行completeUnitOfWork(C)

completeUnitOfWork

执行到这里,想知道发生了什么,就不得不去看completeUnitOfWork的代码了。下面是精简过后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork: Fiber = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;

let next;

next = completeWork(current, completedWork, entangledRenderLanes);

// 这是针对Suspense组件的,暂时可以不看
if (next !== null) {
workInProgress = next;
return;
}

const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}

completedWork = returnFiber;

workInProgress = completedWork;
} while (completedWork !== null);

if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}

我们看上述代码,是有一个循环的,也就是completedWork不为null之前一直执行。此处开始从自己往兄弟、父节点开始遍历。首先对自己执行了completeWork,然后拿到了自己的子节点。如果有子节点,那么将workInProgress赋值为自己的兄弟节点,然后结束当前循环。通常来讲,由于beginWork已经检查了是否有子节点,有的话不会进到complete的流程里面来。通常来讲,只有使用Suspense等特殊场景,才会进到这个分支里去。对于C来说,他没有子节点,不会进入这个循环。

然后取C的兄弟节点,取到了D,则会将workInProgress赋值为D,结束当前循环。等下一次循环的时候,执行performUnitWork(D),流程和前面讲过的一样。

而如果当前节点没有兄弟节点了,例如E,则会将遍历的对象设为父节点,对父节点执行completeWork,重复这个循环。对于D,E这两个节点来说,顺序就是begin D->begin E->complete E->complete D。

最后,构建流程会回到Fiber的根节点,构建结束。

结合performUnitWork和completeUnitOfWork两个方法,用自然语言描述Fiber树的构建流程就是,有子节点就遍历子节点,没有子节点了去找右兄弟节点。兄弟节点也没有了,就回到父节点。

用伪代码来描述(略去complete里重复验证子节点的那一部分),省略函数实参中可计算出来的入参和不重要的入参,可以如下表述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function performUnitOfWork(node){
child = beginWork(node) // 返回子节点

if(child){
workInProgress = child
return //下一次循环
}else{
do{
completeWork(node)
if(node.sibling){
workInProgress = node.sibling
return //下一次循环
}else{
node = node.return // 父节点
}
}while(node)
}
}

while(workInProgress){
performUnitWork(workInprogress)
}

再回去结合那张图,我们其实可以明白,beginWork就是向下走的过程(图中1,2,4)。向下走到头了,会对自己执行completeWork,就会先向右(图中3,6)。向右也走到头了,就会向上走(图中5,7,8)

结语

这一篇,我们从整体上对React构建Fiber tree的流程有了大致的认知,但是我们其实并没有深入了解beginWork和completeWork具体干了什么。后面我们会再深入了解。


通俗易懂的React原理(二):构建Fiber tree的流程
https://miku03090831.github.io/2025/07/26/通俗易懂的React原理(二):构建Fiber-tree的流程/
作者
qh_meng
发布于
2025年7月26日
许可协议