commitWork前,会将在workloopSync中生成的workInProgressfiber树赋值给fiberRootfinishedWork属性。

1
2
3
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;

commitRootImpl(root, renderPriorityLevel)的源码

在输出阶段,commitRoot的实现逻辑是在commitRootImpl函数中,其主要逻辑是处理副作用,执行副作用对应的DOM操作,将最新的 fiber 树结构反映到 DOM 上。除此之外,一些生命周期钩子(比如componentDidXXX)、hook(比如useEffect)需要在commit阶段执行。

commit阶段被分成几个子阶段。对每个阶段的副作用列表做了一个单独的处理,也就是通过调用生命周期函数和hooks进行相应的更新。

commit阶段的主要工作(即Renderer的工作流程)分为三部分:

  • before mutation阶段(执行DOM操作前)
  • mutation阶段(执行DOM操作)
  • layout阶段(执行DOM操作后)
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
function commitRootImpl(root, renderPriorityLevel) {
do {

flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
flushRenderPhaseStrictModeWarningsInDEV();

const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;

if (enableSchedulingProfiler) {
markCommitStarted(lanes);
}

if (finishedWork === null) {
if (enableSchedulingProfiler) {
markCommitStopped();
}

return null;
}
root.finishedWork = null;
root.finishedLanes = NoLanes;


root.callbackNode = null;


let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
//离散事件导致的更新处理=》光标
if (rootsWithPendingDiscreteUpdates !== null) {
if (
!hasDiscreteLanes(remainingLanes) &&
rootsWithPendingDiscreteUpdates.has(root)
) {
rootsWithPendingDiscreteUpdates.delete(root);
}
}

if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
} else {

}

// Get the list of effects.
在归阶段的update时,effects链表的形成,只会挂载自己的子Fiber,所以当前应用的根节点的Effect是没有被挂载在这个链表上的
let firstEffect;
//如果当前应用的根节点存在Effect,需要将当前应用的根节点挂载在Effect链表的最后
if (finishedWork.flags > PerformedWork) {
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
//如果effect list 不存在,第一项就是根Fiber节点
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
firstEffect = finishedWork.firstEffect;
}

if (firstEffect !== null) {
let previousLanePriority;
if (decoupleUpdatePriorityFromScheduler) {
previousLanePriority = getCurrentUpdateLanePriority();
setCurrentUpdateLanePriority(SyncLanePriority);
}

const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);

ReactCurrentOwner.current = null;

focusedInstanceHandle = prepareForCommit(root.containerInfo);
shouldFireAfterActiveInstanceBlur = false;

nextEffect = firstEffect;
//beforemutation执行的工作
do {
try {
commitBeforeMutationEffects();
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}

} while (nextEffect !== null);


focusedInstanceHandle = null;

if (enableProfilerTimer) {

recordCommitTime();
}

nextEffect = firstEffect;
//mutation阶段执行的工作
do {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
}
resetAfterCommit(root.containerInfo);

root.current = finishedWork;

// The next phase is the layout phase, where we call effects that read
// the host tree after it's been mutated. The idiomatic use case for this is
// layout, but class component lifecycles also fire here for legacy reasons.
nextEffect = firstEffect;
//layout阶段的工作
do {

try {
commitLayoutEffects(root, lanes);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);

nextEffect = null;

// Tell Scheduler to yield at the end of the frame, so the browser has an
// opportunity to paint.
requestPaint();

if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;

if (decoupleUpdatePriorityFromScheduler && previousLanePriority != null) {
// Reset the priority to the previous non-sync value.
setCurrentUpdateLanePriority(previousLanePriority);
}
} else {
// No effects.
root.current = finishedWork;
// Measure these anyway so the flamegraph explicitly shows that there were
// no effects.
// TODO: Maybe there's a better way to report this.
if (enableProfilerTimer) {
recordCommitTime();
}
}

const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

if (rootDoesHavePassiveEffects) {
// This commit has passive effects. Stash a reference to them. But don't
// schedule a callback until after flushing layout work.
//和本次更新中的useEffect调用有关
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
// We are done with the effect chain at this point so let's clear the
// nextEffect pointers to assist with GC. If we have passive effects, we'll
// clear this in flushPassiveEffects.
//本次更新不存在useEffect的调用,清理链表,垃圾回收
nextEffect = firstEffect;
while (nextEffect !== null) {
const nextNextEffect = nextEffect.nextEffect;
nextEffect.nextEffect = null;
if (nextEffect.flags & Deletion) {
detachFiberAfterEffects(nextEffect);
}
nextEffect = nextNextEffect;
}
}

// Read this again, since an effect might have updated it
remainingLanes = root.pendingLanes;

// Check if there's remaining work on this root
if (remainingLanes !== NoLanes) {
if (enableSchedulerTracing) {
if (spawnedWorkDuringRender !== null) {
const expirationTimes = spawnedWorkDuringRender;
spawnedWorkDuringRender = null;
for (let i = 0; i < expirationTimes.length; i++) {
scheduleInteractions(
root,
expirationTimes[i],
root.memoizedInteractions,
);
}
}
schedulePendingInteractions(root, remainingLanes);
}
} else {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}

if (enableSchedulerTracing) {
if (!rootDidHavePassiveEffects) {
.
finishPendingInteractions(root, lanes);
}
}

if (remainingLanes === SyncLane) {
//通过计数判断是否是进入了无限循环更新了
if (root === rootWithNestedUpdates) {
nestedUpdateCount++;
} else {
nestedUpdateCount = 0;
rootWithNestedUpdates = root;
}
} else {
nestedUpdateCount = 0;
}

onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel);

//commit可能会出现新的更新,确保在这个根节点上没有新的更新任务
ensureRootIsScheduled(root, now());

if (hasUncaughtError) {
hasUncaughtError = false;
const error = firstUncaughtError;
firstUncaughtError = null;
throw error;
}

if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
if (enableSchedulingProfiler) {
markCommitStopped();
}
return null;
}

// If layout work was scheduled, flush it now.
//如useLayoutEffect中调用setState,这个就是同步的更新,就会在这里面同步执行
flushSyncCallbackQueue();

if (enableSchedulingProfiler) {
markCommitStopped();
}

return null;
}

before mutation阶段

整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理

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
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const current = nextEffect.alternate;
//处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
if ((nextEffect.flags & Deletion) !== NoFlags) {
if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur();
}
} else {
// TODO: Move this out of the hot path using a dedicated effect tag.
if (
nextEffect.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, nextEffect) &&
doesFiberContain(nextEffect, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur();
}
}
}
//调用getSnapshotBeforeUpdate生命周期钩子。
const flags = nextEffect.flags;
if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(nextEffect);

commitBeforeMutationEffectOnFiber(current, nextEffect);

resetCurrentDebugFiberInDEV();
}
//FunctionComponent中useEffect对应的Effect,需要调度passiveeffects的回调函数
if ((flags & Passive) !== NoFlags) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}

整体可以分为三部分:

  • 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
  • 调用getSnapshotBeforeUpdate生命周期钩子。
  • 调度useEffect。

这里忽略第一部分,看调用getSnapshotBeforeUpdate生命周期钩子的部分

commitBeforeMutationEffects方法调用getSnapshotBeforeUpdate

commitBeforeMutationEffectOnFiber是commitBeforeMutationLifeCycles的别名。

在该方法内会调用getSnapshotBeforeUpdate。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
return;
}
case ClassComponent: {
//如果存在 Snapshot 生命周期对应的tag
if (finishedWork.flags & Snapshot) {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
//取到对应的React Component的实例
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentName(finishedWork.type) || 'instance',
);
}
}
}
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
if (__DEV__) {
const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
console.error(
'%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
'must be returned. You have returned undefined.',
getComponentName(finishedWork.type),
);
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
return;
}
case HostRoot: {
if (supportsMutation) {
if (finishedWork.flags & Snapshot) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
}
return;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
}
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}

这个阶段调用getSnapshotBeforeUpdates生命周期,页面没有可见的更新出现。

调度useEffect

1
2
3
4
5
6
7
8
9
10
11
12
if ((flags & Passive) !== NoFlags) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;

scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}

scheduleCallback方法Scheduler模块提供,用于以某个优先级异步调度一个回调函数。 这个函数以一个优先级异步执行回调函数,所以在FunctionComponent中存在useEffect,并且回调函数需要触发的情况,会在beforemutation阶段以normal的优先级调度,由于commit阶段是同步执行的,所以useEffect的回调函数会在commit阶段执行完之后异步执行。

在此处,被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function flushPassiveEffects(): boolean {
// Returns whether passive effects were flushed.
if (pendingPassiveEffectsLanes !== NoLanes) {
const priority = higherEventPriority(
DefaultEventPriority,
lanesToEventPriority(pendingPassiveEffectsLanes),
);
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(priority);
return flushPassiveEffectsImpl();
} finally {
setCurrentUpdatePriority(previousPriority);
}
}
return false;
}

flushPassiveEffects内部会设置优先级,并切调用flushPassiveEffectsImpl

flushPassiveEffectsImpl主要做三件事:

  • 调用该useEffect在上一次render时的销毁函数
  • 调用该useEffect在本次render时的回调函数
  • 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行他

第一步:执行上一次render时useEffect的销毁函数

useEffect的执行需要保证所有组件useEffect的销毁函数必须都执行完后才能执行任意一个组件的useEffect的回调函数。

这是因为多个组件间可能共用同一个ref。

如果在执行前没有销毁函数,那么在某个组件useEffect的销毁函数中修改的ref.current可能影响另一个组件useEffect的回调函数中的同一个ref的current属性。

useLayoutEffect中也有同样的问题,所以他们都遵循先全部销毁,再执行的顺序。

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
const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
for (let i = 0; i < unmountEffects.length; i += 2) {
const effect = ((unmountEffects[i]: any): HookEffect);
const fiber = ((unmountEffects[i + 1]: any): Fiber);
const destroy = effect.destroy;
effect.destroy = undefined;

if (typeof destroy === 'function') {
try {
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startPassiveEffectTimer();
destroy();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
destroy();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}
}

获取在unmount时处理的销毁函数中其中pendingPassiveHookEffectsUnmount数组的索引i保存需要销毁的effect,i+1保存该effect对应的fiber。(向pendingPassiveHookEffectsUnmount数组内push数据的操作发生在layout阶段 commitLayoutEffectOnFiber方法内部的schedulePassiveEffects方法中。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function schedulePassiveEffects(finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const {next, tag} = effect;
if (
(tag & HookPassive) !== NoHookEffect &&
(tag & HookHasEffect) !== NoHookEffect
) {
// 向`pendingPassiveHookEffectsUnmount`数组内`push`要销毁的effect
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
// 向`pendingPassiveHookEffectsMount`数组内`push`要执行回调的effect
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}

第二步:回调函数的执行

遍历数组,执行useEffect的回调函数

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
const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {
const effect = ((mountEffects[i]: any): HookEffect);
const fiber = ((mountEffects[i + 1]: any): Fiber);
try {
const create = effect.create;
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
fiber.mode & ProfileMode
) {
try {
startPassiveEffectTimer();
effect.destroy = create();
} finally {
recordPassiveEffectDuration(fiber);
}
} else {
effect.destroy = create();
}
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}

其中向pendingPassiveHookEffectsMount中push数据的操作同样发生在schedulePassiveEffects中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function schedulePassiveEffects(finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const {next, tag} = effect;
if (
(tag & HookPassive) !== NoHookEffect &&
(tag & HookHasEffect) !== NoHookEffect
) {
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}

需要注意的是:与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。

所以useEffect的这两个阶段会在页面渲染后(layout阶段后)异步执行,以防止同步执行阻塞浏览器渲染。

mutation阶段

这个阶段是执行DOM操作的阶段,遍历effectList,执行commitMutationEffects方法。

1
2
3
4
5
6
7
8
9
10
nextEffect = firstEffect;
do {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);

概览

这部分同样是遍历effectList,根据 ContentReset effectTag重置文字节点,然后更新ref,最后根据effectTag保存的信息进行对应的插入、更新、删除DOM。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
function commitMutationEffects(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
// TODO: Should probably move the bulk of this function to commitWork.
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);

const flags = nextEffect.flags;

if (flags & ContentReset) {
commitResetTextContent(nextEffect);
}

if (flags & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (nextEffect.tag === ScopeComponent) {
commitAttachRef(nextEffect);
}
}
}

// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every possible
// bitmap value, we remove the secondary effects from the effect tag and
// switch on that value.
const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
switch (primaryFlags) {
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
nextEffect.flags &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
nextEffect.flags &= ~Placement;

// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Hydrating: {
nextEffect.flags &= ~Hydrating;
break;
}
case HydratingAndUpdate: {
nextEffect.flags &= ~Hydrating;

// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
commitDeletion(root, nextEffect, renderPriorityLevel);
break;
}
}

resetCurrentDebugFiberInDEV();
nextEffect = nextEffect.nextEffect;
}
}

根据以上代码总结,对每个有副作用的Fiber节点执行如下三个操作:

  • 根据ContentReset effectTag重置文字节点
  • 更新ref
  • 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating(SSR))

Placement

commitPlacement

该方法所做的工作分为三步:
1.获取父级DOM节点。其中finishedWork为传入的Fiber节点

1
2
3
const parentFiber = getHostParentFiber(finishedWork);

const parentStateNode = parentFiber.stateNode;

2.获取Fiber节点的DOM兄弟节点

1
const before = getHostSibling(finishedWork);

3.根据DOM兄弟节点是否存在决定调用parentNode.insertBeforeparentNode.appendChild执行DOM插入操作。

1
2
3
4
5
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}

getHostSibling(获取兄弟DOM节点)的执行很耗时,当在同一个父Fiber节点下依次执行多个插入操作,getHostSibling算法的复杂度为指数级。

这是由于Fiber节点不只包括HostComponent,所以Fiber树和渲染的DOM树节点并不是一一对应的。要从Fiber节点找到DOM节点很可能跨层级遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Item() {
return <li><li>;
}

function App() {
return (
<div>
<Item/>
</div>
)
}

ReactDOM.render(<App/>, document.getElementById('root'));

对应的Fiber树和DOM树结构为:

1
2
3
4
5
6
// Fiber树
child child child child
rootFiber -----> App -----> div -----> Item -----> li

// DOM树
#root ---> div ---> li

当在div的子节点Item前插入一个新节点p,即App变为:

1
2
3
4
5
6
7
8
function App() {
return (
<div>
<p></p>
<Item/>
</div>
)
}

对应的Fiber树和DOM树结构为:

1
2
3
4
5
6
7
8
9
// Fiber树
child child child
rootFiber -----> App -----> div -----> p
| sibling child
| -------> Item -----> li
// DOM树
#root ---> div ---> p
|
---> li

此时DOM节点 p的兄弟节点为li,而Fiber节点 p对应的兄弟DOM节点为:

1
fiberP.sibling.child

fiber p兄弟fiber Item子fiber li

Update

调用的是commitWork

在这里主要关注函数组件和原始类型组件

FunctionComponent mutation

调用 commitHookEffectListUnmount()方法,该方法会遍历effectList,执行所有useLayoutEffect hook的销毁函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & tag) === tag) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}

HostComponent mutation

调用的是commitUpdate

commitUpdate->updateProperties->updateDOMProperties

最终会在updateDOMProperties中将render阶段 completeWork (opens new window)中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。

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
function updateDOMProperties(
domElement: Element,
updatePayload: Array<any>,
wasCustomComponentTag: boolean,
isCustomComponentTag: boolean,
): void {
// TODO: Handle wasCustomComponentTag
for (let i = 0; i < updatePayload.length; i += 2) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === STYLE) {
// 处理 style
setValueForStyles(domElement, propValue);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
//处理 DANGEROUSLY_SET_INNER_HTML
setInnerHTML(domElement, propValue);
} else if (propKey === CHILDREN) {
// 处理 children
setTextContent(domElement, propValue);
} else {
// 处理剩余 props
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
}
}
}

Deletion

如果需要删除节点就会执行commitDeletion方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function commitDeletion(
finishedRoot: FiberRoot,
current: Fiber,
renderPriorityLevel: ReactPriorityLevel,
): void {
if (supportsMutation) {
// Recursively delete all host nodes from the parent.
// Detach refs and call componentWillUnmount() on the whole subtree.
unmountHostComponents(finishedRoot, current, renderPriorityLevel);
} else {
// Detach refs and call componentWillUnmount() on the whole subtree.
commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);
}
const alternate = current.alternate;
detachFiberMutation(current);
if (alternate !== null) {
detachFiberMutation(alternate);
}
}

unmountHostComponents方法中先会获取当前节点的父节点,因为想删除某个节点,需要找到他的父节点

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
let node: Fiber = current;

// Each iteration, currentParent is populated with node's host parent if not
// currentParentIsValid.
let currentParentIsValid = false;

// Note: these two variables *must* always be updated together.
let currentParent;
let currentParentIsContainer;

while (true) {
if (!currentParentIsValid) {
let parent = node.return;
findParent: while (true) {
invariant(
parent !== null,
'Expected to find a host parent. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);
const parentStateNode = parent.stateNode;
switch (parent.tag) {
case HostComponent:
currentParent = parentStateNode;
currentParentIsContainer = false;
break findParent;
case HostRoot:
currentParent = parentStateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
case HostPortal:
currentParent = parentStateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
case FundamentalComponent:
if (enableFundamentalAPI) {
currentParent = parentStateNode.instance;
currentParentIsContainer = false;
}
}
parent = parent.return;
}
currentParentIsValid = true;
}

找到之后会执行commitNestedUnmounts方法,这个方法嵌套了commitUnmout方法,因为当我们删除一个节点,这个节点可能包含一颗子树,一颗子树中的子孙Fiber节点都需要被递归的删除。

对于FunctionComponent方法,会注册需要被执行的useEffect的回调函数,如果存在useEffect的销毁函数存在,也会执行销毁函数。

对于ClassComponent方法,会调用componentWillUnmount生命周期钩子

综上:

layout阶段

commitLayoutEffects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);

const flags = nextEffect.flags;
// 调用生命周期钩子和hook
if (flags & (Update | Callback)) {
const current = nextEffect.alternate;
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}

if (enableScopeAPI) {
// 赋值ref
if (flags & Ref && nextEffect.tag !== ScopeComponent) {
commitAttachRef(nextEffect);
}
} else {
if (flags & Ref) {
commitAttachRef(nextEffect);
}
}

resetCurrentDebugFiberInDEV();
nextEffect = nextEffect.nextEffect;
}

这里的commitLayoutEffectOnFiber是commitLifeCycles方法

与前两个阶段类似,layout阶段也是遍历effectList,最终执行 commitLayoutEffectOnFiber方法

  • 对于ClassComponent,他会通过current === null?区分是mount还是update,调用componentDidMountcomponentDidUpdate
  • 触发状态更新的this.setState的第二个参数回调函数,也会在此时调用
  • 对于FunctionComponent及ForwardRef、React.memo包裹的FunctionComponent,他在这里会调用useLayoutEffect hook的回调函数,因为参数是HookLayout | HookHasEffect,所以只处理由useLayoutEffect()创建的effect,调用effect.create()之后, 将返回值赋值到effect.destroy,并且添加useEffect的销毁函数和回调函数到队列
  • 对于HostRoot,即rootFiber,如果赋值了第三个参数回调函数,也会在此时调用

commitAttachRef

获取DOM实例,更新ref

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
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
let instanceToUse;
switch (finishedWork.tag) {
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
// Moved outside to ensure DCE works with this flag
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
instanceToUse = instance;
}
if (typeof ref === 'function') {
ref(instanceToUse);
} else {
if (__DEV__) {
if (!ref.hasOwnProperty('current')) {
console.error(
'Unexpected ref object provided for %s. ' +
'Use either a ref-setter function or React.createRef().',
getComponentName(finishedWork.type),
);
}
}

ref.current = instanceToUse;
}
}
}

current Fiber树切换

1
root.current = finishedWork;

标志着layout阶段结束。

componentWillUnmount会在mutation阶段执行,此时current Fiber树还指向前一次更新的Fiber树,在生命周期钩子内获取的DOM还是更新前的。

componentDidMountcomponentDidUpdate会在layout阶段执行。此时current Fiber树已经指向更新后Fiber树,在生命周期钩子内获取的DOM就是更新后的。