通俗易懂的React原理(六):completeWork流程

代码以React v19.1.0为例,https://github.com/facebook/react/tree/v19.1.0 ,并且经过简化,仅保留了关键的逻辑,以便于理解流程

completeWork

在前面我们讲构建Fiber tree的时候,我们知道每个节点都需要先经过beginWork方法正向遍历。然后等从子节点反向遍历,回到当前节点的时候,会调用completeWork方法。

beginWork方法里,我们执行了组件的渲染,diff算法比较dom差异,打上各种标记。

那么这一篇,我们来看一下completeWork都做了什么。

completework会根据Fiber的tag类型,来进行不同的处理。我们主要关注函数组件,和HostComponent,以及HostText类型的Fiber节点。

函数组件

1
2
3
4
5
switch (workInProgress.tag) {
case FunctionComponent:
bubbleProperties(workInProgress);
return null;
}

对于函数组件,只需要执行bubbleProperties方法。

bubbleProperties

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function bubbleProperties(completedWork: Fiber) {
const didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child;

let newChildLanes: Lanes = NoLanes;
let subtreeFlags = NoFlags;

if (!didBailout) {
// Bubble up the earliest expiration time.
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

// Update the return pointer so the tree is consistent. This is a code
// smell because it assumes the commit phase is never concurrent with
// the render phase. Will address during refactor to alternate model.
child.return = completedWork;

child = child.sibling;
}

completedWork.subtreeFlags |= subtreeFlags;
} else {
// Bubble up the earliest expiration time.
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

// "Static" flags share the lifetime of the fiber/hook they belong to,
// so we should bubble those up even during a bailout. All the other
// flags have a lifetime only of a single render + commit, so we should
// ignore them.
subtreeFlags |= child.subtreeFlags & StaticMask;
subtreeFlags |= child.flags & StaticMask;

// Update the return pointer so the tree is consistent. This is a code
// smell because it assumes the commit phase is never concurrent with
// the render phase. Will address during refactor to alternate model.
child.return = completedWork;

child = child.sibling;
}


completedWork.subtreeFlags |= subtreeFlags;
}

completedWork.childLanes = newChildLanes;

return didBailout;
}

我们来看看bubbleProperties方法,首先判断能否bailout。如果当前的节点不是首次挂载,且子节点是复用current的子节点,就认为可以bailout(复用子节点的逻辑在beginWork,如果props和context都没发生变化的分支里。之前讲beginWork的时候没有细讲)。

我们先看一下不能bailout的逻辑。

首先,把所有子节点、以及子节点的子树的lanes做并集,其实就是把当前节点的所有子树上的节点剩余的lanes做个并集,收集起来,就像冒泡一样。

顺便一提,子节点的lanes被赋值,是在finishQueueingConcurrentUpdates方法里被完成的,那时其实也进行了一次冒泡。

bubbleProperties的冒泡,是为了收集render阶段后,剩余的lanes。beginWork开始的时候,会把lanes清空。而后在updateReducer之类的hooks里,会把跳过的lanes再加回来,这些就是剩余的lanes。bubbleProperties主要就是为了收集这些被加回来的,遗留下来的lanes,给下一次渲染用的。

然后,再把所有子树上的节点的flags也做并集,收集起来,依旧是冒泡到当前节点上。

这两个冒泡,其实就是这个方法名字bubbleProperties的含义。

如果是能够bailout的逻辑,区别只在于flags的冒泡,收集lanes的逻辑依旧没变。收集flags的时候,只收集static类型的flags。如注释所言,static类型的flags就是会跨越生命周期存在的。意思就是,即便这些child节点没有发生变化,但是由于一些flags是跨越生命周期有效的,所以也需要保留下来。

最后,把子节点的lanes和flags都冒泡到当前节点,就完成了bubbleProperties的逻辑。

HostComponent

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
34
35
36
37
38
39
40
41
42
43
44
switch (workInProgress.tag){
case HostComponent: {
popHostContext(workInProgress);
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
renderLanes,
);
} else {
const currentHostContext = getHostContext();

const rootContainerInstance = getRootHostContainer();
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);

markCloned(workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;

if (
finalizeInitialChildren(
instance,
type,
newProps,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
bubbleProperties(workInProgress);

return null;
}
}

对于HostComponent节点,如果不是有次mount,而是更新,那么就打上Update的flag就好了。

如果是首次mount,那么则需要创建dom实例,并且会递归把所有host类型的子节点也加到dom实例里,挂到当前Fiber的stateNode上,并且暂时不加到document上。即,只创建dom节点,而没有真的把dom插入到页面上。

最后,也要像函数组件一样,执行bubbleProperties方法。

HostText

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
switch (workInProgress.tag){
case HostText: {
const newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
markCloned(workInProgress);
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
bubbleProperties(workInProgress);
return null;
}
}

而HostText的处理就简单很多,如果不是mount而是update,就更新文本节点里面的文本。

如果是初次mount,那么就创建文本节点实例,挂到Fiber的stateNode字段。

最后依旧执行bubbleProperties方法

结语

以上就是completeWork方法的逻辑。到现在我们已经几乎把beginWork到completeWork的逻辑全看完了,也就是render阶段的逻辑其实已经大致了解。下一篇我们串一下流程,然后就结束render阶段的学习。


通俗易懂的React原理(六):completeWork流程
https://miku03090831.github.io/2025/09/03/通俗易懂的React原理(六):completeWork流程/
作者
qh_meng
发布于
2025年9月3日
许可协议