0%

react18.2源码分析(三)render-render阶段

react源码分析(三)render-render阶段

如果以下代码没有特殊标记,路径都是 src/react/packages/react-reconciler/src/ReactFiberWorkLoop.js

这个 performConcurrentWorkOnRoot 函数中包含两个重要的阶段,rendercommit 阶段。这是每个并发任务的入口点。

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
// This is the entry point for every concurrent task, i.e. anything that goes through Scheduler.
export function performConcurrentWorkOnRoot(
root: FiberRoot,
didTimeout: boolean,
): RenderTaskFn | null {
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}

// Flush any pending passive effects before deciding which lanes to work on,
// in case they schedule additional work.
const originalCallbackNode = root.callbackNode;
// ??
const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
// Something in the passive effect phase may have canceled the current task.
// Check if the task node for this root was changed.
if (root.callbackNode !== originalCallbackNode) {
// The current task was canceled. Exit. We don't need to call
// `ensureRootIsScheduled` because the check above implies either that
// there's a new task, or that there's no remaining work on this root.
return null;
} else {
// Current task was not canceled. Continue.
}
}

// Determine the next lanes to work on, using the fields stored
// on the root.
// TODO: This was already computed in the caller. Pass it as an argument.
let lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);

if (lanes === NoLanes) {
// Defensive coding. This is never expected to happen.
return null;
}

// We disable time-slicing in some cases: if the work has been CPU-bound
// for too long ("expired" work, to prevent starvation), or we're in
// sync-updates-by-default mode.
// 在某些情况下,我们会禁用时间片切片:如果work过长时间做计算(为了防止饥饿而将其视为“过期”的work),或者我们处于默认启用同步更新模式。
// TODO: We only check `didTimeout` defensively, to account for a Scheduler
// bug we're still investigating. Once the bug in Scheduler is fixed,
// we can remove this, since we track expiration ourselves.
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) && // 参考这个例子,UseDeferredValuePage
(disableSchedulerTimeoutInWorkLoop || !didTimeout);

// ! 1. render
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes) // 参考这个例子,UseDeferredValuePage
: renderRootSync(root, lanes); // ? sy, 不用时间切片

if (exitStatus !== RootInProgress) {
let renderWasConcurrent = shouldTimeSlice;
do {
if (exitStatus === RootDidNotComplete) {
// The render unwound without completing the tree. This happens in special
// cases where need to exit the current render without producing a
// consistent tree or committing.
markRootSuspended(root, lanes, NoLane);
} else {
// ! 2. render结束,做一些检查
// The render completed.

// Check if this render may have yielded to a concurrent event, and if so,
// confirm that any newly rendered stores are consistent.
// TODO: It's possible that even a concurrent render may never have yielded
// to the main thread, if it was fast enough, or if it expired. We could
// skip the consistency check in that case, too.
const finishedWork: Fiber = (root.current.alternate: any);
if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
// A store was mutated in an interleaved event. Render again,
// synchronously, to block further mutations.
exitStatus = renderRootSync(root, lanes);
// We assume the tree is now consistent because we didn't yield to any
// concurrent events.
renderWasConcurrent = false;
// Need to check the exit status again.
continue;
}

// Check if something threw
if (exitStatus === RootErrored) {
const originallyAttemptedLanes = lanes;
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes,
);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
errorRetryLanes,
);
renderWasConcurrent = false;
}
}
if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes, NoLane);
ensureRootIsScheduled(root);
throw fatalError;
}

// !3. commit
// 我们现在有了一个一致的树。下一步要么是 commit,要么是,如果有什么被暂停了,就等待一段时间后再 commit。
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
break;
} while (true);
}

ensureRootIsScheduled(root);
return getContinuationForRoot(root, originalCallbackNode);
}

1. render阶段 - renderRootSync

进入 render 阶段的,同步渲染模式。

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
function renderRootSync(root: FiberRoot, lanes: Lanes) {
// ! 1. 记录 render阶段 开始
const prevExecutionContext = executionContext;
executionContext |= RenderContext;

const prevDispatcher = pushDispatcher(root.containerInfo);
const prevCacheDispatcher = pushCacheDispatcher();

// If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
const memoizedUpdaters = root.memoizedUpdaters;
if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
}

// At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same priority with different updaters.
// That way we can keep the current update and future updates separate.
movePendingFibersToMemoized(root, lanes);
}
}

// ! 2. workInProgressTransitions赋值(用于dev环境)
workInProgressTransitions = getTransitionsForLanes(root, lanes);
// ! 3. 初始化
prepareFreshStack(root, lanes);
}

if (enableSchedulingProfiler) {
markRenderStarted(lanes);
}

let didSuspendInShell = false;
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
// The work loop is suspended. During a synchronous render, we don't
// yield to the main thread. Immediately unwind the stack. This will
// trigger either a fallback or an error boundary.
// TODO: For discrete and "default" updates (anything that's not
// flushSync), we want to wait for the microtasks the flush before
// unwinding. Will probably implement this using renderRootConcurrent,
// or merge renderRootSync and renderRootConcurrent into the same
// function and fork the behavior some other way.
const unitOfWork = workInProgress;
const thrownValue = workInProgressThrownValue;
switch (workInProgressSuspendedReason) {
case SuspendedOnHydration: {
// Selective hydration. An update flowed into a dehydrated tree.
// Interrupt the current render so the work loop can switch to the
// hydration lane.
resetWorkInProgressStack();
workInProgressRootExitStatus = RootDidNotComplete;
break outer;
}
case SuspendedOnImmediate:
case SuspendedOnData: {
if (!didSuspendInShell && getSuspenseHandler() === null) {
didSuspendInShell = true;
}
// Intentional fallthrough
}
default: {
// Unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
break;
}
}
}
// ! 4. workLoopSync
workLoopSync();
break;
} catch (thrownValue) {
handleThrow(root, thrownValue);
}
} while (true);
// Check if something suspended in the shell. We use this to detect an
// infinite ping loop caused by an uncached promise.
//
// Only increment this counter once per synchronous render attempt across the
// whole tree. Even if there are many sibling components that suspend, this
// counter only gets incremented once.
if (didSuspendInShell) {
root.shellSuspendCounter++;
}
// ! 5. 重置
// 重置Context的相关值,如currentlyRenderingFiber等
resetContextDependencies();
// 重置 executionContext
executionContext = prevExecutionContext;

popDispatcher(prevDispatcher);
popCacheDispatcher(prevCacheDispatcher);

if (workInProgress !== null) {
// This is a sync render, so we should have finished the whole tree.
throw new Error(
'Cannot commit an incomplete root. This error is likely caused by a ' +
'bug in React. Please file an issue.',
);
}

if (enableSchedulingProfiler) {
markRenderStopped();
}

// Set this to null to indicate there's no in-progress render.
// 设置为null,表示没有进行中的render了
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;

// ! 6. 再遍历一遍更新队列
// It's safe to process the queue now that the render phase is complete.
finishQueueingConcurrentUpdates();

return workInProgressRootExitStatus;
}

1.1 记录 render 阶段 开始

1
2
const prevExecutionContext = executionContext;
executionContext |= RenderContext;

1.2 workInProgressTransitions赋值

1
workInProgressTransitions = getTransitionsForLanes(root, lanes);

1.3 prepareFreshStack 初始化

初始化 workInProgressRootworkInProgressworkInProgressRootRenderLanes 等值。

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
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
root.finishedWork = null;
root.finishedLanes = NoLanes;

const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
root.timeoutHandle = noTimeout;
// $FlowFixMe[incompatible-call] Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
const cancelPendingCommit = root.cancelPendingCommit;
if (cancelPendingCommit !== null) {
root.cancelPendingCommit = null;
cancelPendingCommit();
}

resetWorkInProgressStack();
workInProgressRoot = root; // FiberRoot

const rootWorkInProgress = createWorkInProgress(root.current, null); // Fiber

workInProgress = rootWorkInProgress; // Fiber
workInProgressRootRenderLanes = lanes;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressRootDidAttachPingListener = false;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootFatalError = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressDeferredLane = NoLane;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
workInProgressRootDidIncludeRecursiveRenderUpdate = false;

// Get the lanes that are entangled with whatever we're about to render. We
// track these separately so we can distinguish the priority of the render
// task from the priority of the lanes it is entangled with. For example, a
// transition may not be allowed to finish unless it includes the Sync lane,
// which is currently suspended. We should be able to render the Transition
// and Sync lane in the same batch, but at Transition priority, because the
// Sync lane already suspended.

entangledRenderLanes = getEntangledLanes(root, lanes);

// 把concurrentQueues的内容添加到fiber的queue中,即给fiber的lanes、childLanes赋值
finishQueueingConcurrentUpdates();

return rootWorkInProgress;
}

1.4.1 workLoopSync

1
2
3
4
5
6
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}

来自 renderRootSync,while循环,只要workInProgress有值,就会一直循环执行performUnitOfWork,直到workInProgress === null。

所以同步渲染模式,一旦开始这个Fiber Reconciler协调流程,就会进入一直循环的逻辑,直到创建出完整的FiberTree,这个过程无法中断。

下面我们来看看并发渲染模式的方法。

1.4.2 workLoopConcurrent

1
2
3
4
5
6
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}

来自 renderRootConcurrent,可以看见和同步渲染模式基本一样,唯一的区别就是while循环的多了一个判断条件:当前程序运行时间是否小于帧间隔时间frameInterval【默认5ms】。

  • 如果小于:则返回false,代表还有剩余可执行时间。取反后为true,表示可以继续创建FiberTree的工作循环。
  • 如果大于:则返回true,代表没有剩余可执行时间。取反后为false,则while循环的条件将不再满足,会暂停创建FiberTree的工作,结束本次宏任务,剩下的工作会留待下一次宏任务再处理。

所以并发渲染模式和同步渲染模式的主要区别就是在这里:它可以中断FiberTree的创建过程,而同步渲染模式是无法中断这个过程的,它只能从开始到创建完成。

并且在这里我们也可以看出,它们的while循环里面都是执行了一个相同的performUnitOfWork方法,所以后面的逻辑也就没有什么模式的区分了,都是执行的一样的内容。

1.4.3 并发模式下暂停工作与恢复执行

packages\react-reconciler\src\ReactFiberWorkLoop.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 并发渲染模式
function renderRootConcurrent() {
do {
try {
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);

// 检查tree工作是否完成
if (workInProgress !== null) {
// Still work remaining.
return RootInProgress;
}
}

workLoopConcurrent 并发渲染的工作被中断后,就会退出 do while 循环,然后就会检查当前的 workInProgress 是否为 null,很明显被暂停的情况,workInProgress 是一定有值的,它的值为下一个即将处理的 Fiber 节点。

此时 workInProgress 不为 null,就会返回 RootInProgress 的渲染状态,表示还有剩下的工作。

这里 return 之后,就会回到 performConcurrentWorkOnRoot 方法之中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function performConcurrentWorkOnRoot(root, didTimeout) {
// 返回未完成的渲染状态
let exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);

// 根据渲染状态 继续执行逻辑
if (exitStatus !== RootInProgress) {
// ...
}

// 渲染未完成的情况:
if (root.callbackNode === originalCallbackNode) {
// 渲染暂停的情况:继续返回performConcurrentWorkOnRoot,下一次宏任务继续处理
return performConcurrentWorkOnRoot.bind(null, root);
}
}
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
function workLoop() {
// ...
while (currentTask !== null) {

if (currentTask.expirationTime > currentTime && (!hasTimeRemaining || shouldYieldToHost()) ) {
// This currentTask hasn't expired, and we've reached the deadline.
break;
}
// ...

// callback就是performConcurrentWorkOnRoot函数
const continuationCallback = callback(didUserCallbackTimeout);
// continuationCallback 如果返回也是 performConcurrentWorkOnRoot函数
if (typeof continuationCallback === 'function') {
// 说明任务还未完成,将任务继续设置未当前任务的callback,等待下次继续执行
// 这里没有删除这个任务,则下次取出的第一个任务,还是这个任务,
currentTask.callback = continuationCallback;
}
}

if (currentTask !== null) {
// 还有工作,则会生成一个新的宏任务,在下次的宏任务中继续执行剩下的任务
return true;
}
else {
// ...
}
}

在workLoop方法之中,就会将返回的内容再次赋值给当前任务currentTask的callback属性,表示还有未完成的工作。workLoop方法中的while循环就会被中断,原因同样是shouldYieldToHost方法。

在workLoop方法最后就会判断当前任务currentTask是否为null,很明显被中断的情况,currentTask是有值的,所以就会返回true,这里会一直向上return:

最后会将结果返回performWorkUntilDeadline方法之中:

1
2
3
4
5
6
7
8
9
10
11
const performWorkUntilDeadline = () => {
// ...
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
// 此时hasMoreWork为true
if (hasMoreWork) {
// 如果还有任务,则又触发创建宏任务事件,生成新的宏任务,即在下一个消息事件继续执行任务
schedulePerformWorkUntilDeadline();
} else {
// ...
}
}

此时hasMoreWork为true,就会调用schedulePerformWorkUntilDeadline方法,生成一个新的宏任务,在下个宏任务中继续执行剩下的任务。

恢复执行的原理也很简单:在下次的宏任务中继续创建FiberTree的时候,因为workInProgress是一个全局变量,一直保存在内存之中,并且它内容为下一个即将处理的Fiber节点,所以下次再回到这里时,就会自动从这个Fiber节点开始继续执行剩下的创建工作。

1.5 performUnitOfWork

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
function performUnitOfWork(unitOfWork: Fiber): void {
// 缓存的上一次的Fiber
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);

// 开始工作
let next = beginWork(current, unitOfWork, entangledRenderLanes);

resetCurrentDebugFiberInDEV();
// ! 把pendingProps更新到memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;

/**
* 注意:当遍历到叶子元素,即没有子FiberNode时,performUnitOfWork会进入归的阶段。
* 这个阶段会调用completeUnitOfWork处理FiberNode,当某个FiberNode阶段执行completeUnitOfWork方法后,
* 如果存在其他的兄弟节点【FiberNode.sibling !== null】,会进入兄弟节点的归阶段,
* 如果不存在其他兄弟节点,则进入父节点的归阶段,递阶段和归阶段会交替进行直到HostFiber的归阶段
* 至此,render工作结束,Fiber树创建完成。
*
*/
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 如果不再产生新的work,那么当前work结束
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}

// 当前 Fiber 为null
ReactCurrentOwner.current = null;
}

Fiber 树结构

1
2
3
4
5
6
7
8
// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// App.js

import MyFun from './views/MyFun';
import MyClass from './views/MyClass';

export default function App() {
console.log('App组件运行了')
return (
<div className="App">
<div>react源码调试</div>
<MyFun name='MyFun'></MyFun>
<MyClass name='MyClass'></MyClass>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//  MyFun.js
import React from 'react'
import { useState } from 'react'

export default function MyFun(props) {
console.log('MyFun组件运行了')
const [count, setCount] = useState(1)
return (
<div className='MyFun'>
<div>MyFun组件</div>
<div>state: {count}</div>
<div>name: {props.name}</div>
</div>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MyClass.js
import React, { Component } from 'react';

export default class MyClass extends Component {
constructor(props) {
super(props)
console.log('MyClass组件运行了')
this.state = {
count: 1
}
}
componentDidMount() {
console.log('MyClass组件mount完成')
}
render() {
return (
<div className='MyClass'>
<div>MyClass组件</div>
<div>state: {this.state.count}</div>
<div>name: {this.props.name}</div>
</div>
);
}
}

alt text

1.5.1 beginWork

简单来说,就是生成 fiber 树。

packages\react-reconciler\src\ReactFiberBeginWork.js

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
let didReceiveUpdate: boolean = false;

function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// update更新流程
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;

if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
didReceiveUpdate = true;
} else {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
// If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;

if (getIsHydrating() && isForkedChild(workInProgress)) {
// Check if this child belongs to a list of muliple children in
// its parent.
//
// In a true multi-threaded implementation, we would render children on
// parallel threads. This would represent the beginning of a new render
// thread for this subtree.
//
// We only use this for id generation during hydration, which is why the
// logic is located in this special branch.
const slotIndex = workInProgress.index;
const numberOfForks = getForksAtLevel(workInProgress);
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}

// Before entering the begin phase, clear pending update priority.
// TODO: This assumes that we're about to evaluate the component and process
// the update queue. However, there's an exception: SimpleMemoComponent
// sometimes bails out later in the begin phase. This indicates that we should
// move this assignment out of the common path and into each branch.
// 在进入开始阶段之前,清除待处理的更新优先级。
// TODO: 这假设我们即将评估组件并处理更新队列。然而,有一个例外情况:SimpleMemoComponent
// 有时会在开始阶段后退出。这表明我们应该将这个赋值移出常规路径,放到每个分支中。
workInProgress.lanes = NoLanes;

switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
// 函数组件
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// 类组件
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// 根Fiber(第一次都会走这里)
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostHoistable:
if (enableFloat && supportsResources) {
return updateHostHoistable(current, workInProgress, renderLanes);
}
case HostSingleton:
if (supportsSingletons) {
return updateHostSingleton(current, workInProgress, renderLanes);
}
// 原生标签,div span等标签
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case Mode:
return updateMode(current, workInProgress, renderLanes);
case Profiler:
return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(current, workInProgress, renderLanes);
}
break;
}
case OffscreenComponent: {
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
return updateLegacyHiddenComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
case CacheComponent: {
if (enableCache) {
return updateCacheComponent(current, workInProgress, renderLanes);
}
break;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
return updateTracingMarkerComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
}

throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
'React. Please file an issue.',
);
}
1.5.1.1 updateHostRoot
  1. 更新当前 fiber,比如 props/state 更新,生命周期函数执行、Hooks 函数执行等。
  2. 返回一个下一个 fiber。

packages\react-reconciler\src\ReactFiberBeginWork.js

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
function updateHostRoot(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {

pushHostRootContext(workInProgress);

// current是根fiber,current的的stateNode是FiberRoot
if (current === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}

// ! 1. 更新props、state、updateQuue、transition
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState.element;
// 把current上的updateQueue复用到 workInProgress 上一份。
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);

const nextState: RootState = workInProgress.memoizedState;
const root: FiberRoot = workInProgress.stateNode;
pushRootTransition(workInProgress, root, renderLanes);

// ...

const nextChildren = nextState.element;

// ! 2. bailout or 协调子节点,如果新旧children相同,则会进入优化的逻辑,跳过本次reconcile协调流程,复用原来的节点内容
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
reconcileChildren(current, workInProgress, nextChildren, renderLanes);

// ! 3. 返回子节点
return workInProgress.child;
}
1.5.1.2 updateHostComponent

packages\react-reconciler\src\ReactFiberBeginWork.js

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
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContext(workInProgress);

if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}

const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;

let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);

if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.flags |= ContentReset;
}

if (enableFormActions && enableAsyncActions) {
const memoizedState = workInProgress.memoizedState;
if (memoizedState !== null) {
// This fiber has been upgraded to a stateful component. The only way
// happens currently is for form actions. We use hooks to track the
// pending and error state of the form.
//
// Once a fiber is upgraded to be stateful, it remains stateful for the
// rest of its lifetime.
const newState = renderTransitionAwareHostComponentWithHooks(
current,
workInProgress,
renderLanes,
);

// If the transition state changed, propagate the change to all the
// descendents. We use Context as an implementation detail for this.
//
// This is intentionally set here instead of pushHostContext because
// pushHostContext gets called before we process the state hook, to avoid
// a state mismatch in the event that something suspends.
//
// NOTE: This assumes that there cannot be nested transition providers,
// because the only renderer that implements this feature is React DOM,
// and forms cannot be nested. If we did support nested providers, then
// we would need to push a context value even for host fibers that
// haven't been upgraded yet.
if (isPrimaryRenderer) {
HostTransitionContext._currentValue = newState;
} else {
HostTransitionContext._currentValue2 = newState;
}
if (enableLazyContextPropagation) {
// In the lazy propagation implementation, we don't scan for matching
// consumers until something bails out.
} else {
if (didReceiveUpdate) {
if (current !== null) {
const oldStateHook: Hook = current.memoizedState;
const oldState: TransitionStatus = oldStateHook.memoizedState;
// This uses regular equality instead of Object.is because we assume
// that host transition state doesn't include NaN as a valid type.
if (oldState !== newState) {
propagateContextChange(
workInProgress,
HostTransitionContext,
renderLanes,
);
}
}
}
}
}
}

markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
1.5.1.3 bailout

bailout阶段,返回null。

不会发生在组件初次渲染阶段,仅仅发生在组件更新阶段。当组件⼦节点没有发⽣变化,或者是被⼿动挡住(如类组件的 shouldComponentUpdate、memo 等),组件⼦节点不需要协调的时候。

1.5.1.4 协调子节点 reconcileChildren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
// 组件初次挂载
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// 组件更新
workInProgress.child = reconcileChildFibers(
workInProgress, // 父节点
current.child, // 旧的child
nextChildren, // 新的child
renderLanes, // 本次更新的lanes
);
}
}

其实最终执行的函数是同一个,都是 createChildReconciler,而 createChildReconciler 是一个 wrapper function。最终会返回内部的 reconcileChildFibers 函数。

1
2
3
export const reconcileChildFibers: ChildReconciler =
createChildReconciler(true);
export const mountChildFibers: ChildReconciler = createChildReconciler(false);
1.5.1.5 reconcileChildFibers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// * 协调子节点,构建新的子fiber结构,并且返回新的子fiber
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null, // 老fiber的第一个子节点
newChild: any,
lanes: Lanes,
): Fiber | null {
// ...

const firstChildFiber = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
newChild,
lanes,
null, // debugInfo
);

// ...
return firstChildFiber;
}
1.5.1.6 reconcileChildFibersImpl
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
function reconcileChildFibersImpl(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
debugInfo: ReactDebugInfo | null,
): Fiber | null {
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// 如果newChild是Fragment类型,且没有key,则直接协调其子节点
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}

// 根据新的child类型,进行不同的处理
// 单个节点、数组、迭代器、promise、context处理,最常见的react-element元素类型:react中类组件,函数组件,普通dom组件都属于此类型
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
mergeDebugInfo(debugInfo, newChild._debugInfo),
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes,
debugInfo,
),
);
case REACT_LAZY_TYPE:
const payload = newChild._payload;
const init = newChild._init;
return reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
init(payload),
lanes,
mergeDebugInfo(debugInfo, newChild._debugInfo),
);
}
// 2,数组节点的处理,即多个子节点,比如div.App下面有三个子节点
// 循环创建多个子节点,最后返回第一个子节点,firstChild
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
mergeDebugInfo(debugInfo, newChild._debugInfo),
);
}

if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
lanes,
mergeDebugInfo(debugInfo, newChild._debugInfo),
);
}

// Usables are a valid React node type. When React encounters a Usable in
// a child position, it unwraps it using the same algorithm as `use`. For
// example, for promises, React will throw an exception to unwind the
// stack, then replay the component once the promise resolves.
//
// A difference from `use` is that React will keep unwrapping the value
// until it reaches a non-Usable type.
//
// e.g. Usable<Usable<Usable<T>>> should resolve to T
//
// The structure is a bit unfortunate. Ideally, we shouldn't need to
// replay the entire begin phase of the parent fiber in order to reconcile
// the children again. This would require a somewhat significant refactor,
// because reconcilation happens deep within the begin phase, and
// depending on the type of work, not always at the end. We should
// consider as an future improvement.
if (typeof newChild.then === 'function') {
const thenable: Thenable<any> = (newChild: any);
return reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
unwrapThenable(thenable),
lanes,
mergeDebugInfo(debugInfo, thenable._debugInfo),
);
}

if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = (newChild: any);
return reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
readContextDuringReconcilation(returnFiber, context, lanes),
lanes,
debugInfo,
);
}

throwOnInvalidObjectType(returnFiber, newChild);
}

// 3,处理文本子节点
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number'
) {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
}

// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
1.5.1.6 reconcileSingleElement
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
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
debugInfo: ReactDebugInfo | null,
): Fiber {
const key = element.key;
let child = currentFirstChild;

// 检查老的fiber单链表中是否有可以复用的节点
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
// 新老都是Fragment
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
return existing;
}
} else {
if (
child.elementType === elementType ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false) ||
// Lazy types should reconcile their resolved type.
// We need to do this after the Hot Reloading check above,
// because hot reloading has different semantics than prod because
// it doesn't resuspend. So we can't let the call below suspend.
(typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === child.type)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
coerceRef(returnFiber, child, existing, element);
existing.return = returnFiber;
return existing;
}
}
// 如果key相同,类型不相同。不再查找
deleteRemainingChildren(returnFiber, child);
break;
} else {
// 这个老节点没法复用
deleteChild(returnFiber, child);
}
child = child.sibling;
}

// 1. 初次挂载 2. 没有找到可以复用的老节点
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 根据React-element元素对象,创建FiberNode节点
const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(returnFiber, currentFirstChild, created, element);
created.return = returnFiber;
return created;
}
}
1.5.1.7 createFiberFromElement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let owner = null;
const type = element.type; // 存储原始的组件内容,比如function,class
const key = element.key;
const pendingProps = element.props; // 等待处理的,最新的props

// 创建Fiber节点: 根据元素的type ,针对组件来说:type值一般为class类 或者Function函数
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
return fiber;
}

createFiberFromElement 方法很重要,react 所有 reactElement 元素都是通过这个方法来创建对应的 Fiber 节点。比如常见的 class组件,function组件,原生dom元素等。我们在项目中定义的组件和元素在被编译之后,最终在加载时都会通过jsxRuntime.jsx方法转换为一个reactElement元素对象。

1.5.1.8 createFiberFromTypeAndProps
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
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let fiberTag = IndeterminateComponent;
let resolvedType = type;

// 根据元素的type进行不同的逻辑处理,class也是函数
if (typeof type === 'function') {
// 针对类组件的特殊处理
if (shouldConstruct(type)) {
// 根据type 设置fiberTag 为class组件
fiberTag = ClassComponent;
}
// 普通函数组件,没有更新tag【重要,伏笔】
} else if (typeof type === 'string') {
// 原生dom的处理
fiberTag = HostComponent;
} else {
// 其他type类型的处理
// ...
}

// 创建Fiber
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;

return fiber;
}

在react应用中我们定义的class组件,都是通过继承react的内部组件Component来定义的,所以通过它原型上的isReactComponent属性可以来区分当前组件是不是类组件,返回true则是类组件,返回false则是函数组件。

如果是类组件就会更新fiberTag为ClassComponent的值。

如果是函数组件,这里并没有更新fiberTag,所以函数组件的fiberTag还是为待定的IndeterminateComponent的值。

最后是针对普通的dom元素的处理,更新为HostComponent的值。

1.5.1.9 多子节点的创建 - reconcileChildrenArray
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
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<any>,
lanes: Lanes,
debugInfo: ReactDebugInfo | null,
): Fiber | null {
let resultingFirstChild: Fiber | null = null; // 存储新生成的child
let previousNewFiber: Fiber | null = null; // 上一个新建的child

let oldFiber = currentFirstChild; // 旧的Fiber节点
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// ! 1. 从左边往右遍历,比较新老节点,如果节点可以复用,继续往右,否则就停止
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
debugInfo,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}

// !2.1 新节点没了,(老节点还有)。则删除剩余的老节点即可
// 0 1 2 3 4
// 0 1 2 3
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
// ! 2.2 (新节点还有),老节点没了
// 0 1 2 3 4
// 0 1 2 3 4 5
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
lanes,
debugInfo,
);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}


// !2.3 新老节点都还有节点,但是因为老fiber是链表,不方便快速get与delete,
// ! 因此把老fiber链表中的节点放入Map中,后续操作这个Map的get与delete
// 0 1| 4 5
// 0 1| 7 8 2 3
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
debugInfo,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}

// !3. 如果是组件更新阶段,此时新节点已经遍历完了,能复用的老节点都用完了,
// ! 则最后查找Map里是否还有元素,如果有,则证明是新节点里不能复用的,也就是要被删除的元素,此时删除这些元素就可以了
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach(child => deleteChild(returnFiber, child));
}

if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
总结

从 hostFiber 根节点开始,react 的深度优先遍历算法,会一直创建到再无 Child 子节点。

此时叶子结点所对应的Fiber节点创建完成后,它会继续开启属于它的 beginWork 工作,来创建它的子节点,但是因为它已经没有子节点了,所以它最终会返回一个null。

然后一直向上return,直到回到performUnitOfWork方法中:将进入此 Fiber 节点的 completeWork 工作,也就是归的阶段

1.5.2 completeUnitOfWork

completeWork 代表一个 Fiber 节点的结束工作,completeUnitOfWork 是 completeWork 阶段的入口函数。简单来说,就是生成 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
function completeUnitOfWork(unitOfWork: Fiber): void {
// 取出当前的Fiber节点
let completedWork: Fiber = unitOfWork;
do {
// 取出节点对应的current
const current = completedWork.alternate;
// 取出节点的父级节点
const returnFiber = completedWork.return;

let next;
if (!enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode) {
next = completeWork(current, completedWork, entangledRenderLanes);
} else {
startProfilerTimer(completedWork);
next = completeWork(current, completedWork, entangledRenderLanes);
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}

if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
workInProgress = next;
return;
}

// 一个节点的completeWork工作完成后,寻找是否存在兄弟节点
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 如果兄弟节点存在,则设置为新的workInProgress,跳出当前函数,回到performUnitOfWork方法中
// 这将开始sibling兄弟节点的beginWork工作【自上而下】
workInProgress = siblingFiber;
return;
}
// 【自下而上的处理】
// 如果不存在兄弟节点,则将父级节点设置为completedWork,开始父级节点的completedWork工作
completedWork = returnFiber;
// 更新workInProgress,可能会开启新的beginWork工作
workInProgress = completedWork;
} while (completedWork !== null);

// We've reached the root.
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}

completeUnitOfWork方法中重点就是一个do while循环:completedWork !== null。这个循环的条件是completedWork不等于null,即它是一个有效的Fiber节点。

1.5.2.1 completeWork

根据tag值区别不同fiber
alt text

1.5.2.2 bubbleProperties

向上冒泡 subtreeFlags 和 childLanes

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
function bubbleProperties(completedWork: Fiber) {
const didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child;

let newChildLanes = NoLanes;
let subtreeFlags = NoFlags;

if (!didBailout) {
// ? sy
// Bubble up the earliest expiration time.
// “向上冒泡”最早的过期时间
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// In profiling mode, resetChildExpirationTime is also used to reset
// profiler durations.
let actualDuration = completedWork.actualDuration;
let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);
// ! 遍历completedWork的所有子节点
let child = completedWork.child;
while (child !== null) {
// ! 1. 将他们的lanes和childLanes合并到newChildLanes中
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);
// ! 2. 将他们的subtreeFlags和flags合并到subtreeFlags中
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

actualDuration += child.actualDuration;

treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}

completedWork.actualDuration = actualDuration;
completedWork.treeBaseDuration = treeBaseDuration;
} else {
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

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

child.return = completedWork;
child = child.sibling;
}
}
// ! 子树的flags
completedWork.subtreeFlags |= subtreeFlags;
} else {
// Bubble up the earliest expiration time.
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// In profiling mode, resetChildExpirationTime is also used to reset
// profiler durations.
let treeBaseDuration = ((completedWork.selfBaseDuration: any): number);

let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

subtreeFlags |= child.subtreeFlags & StaticMask;
subtreeFlags |= child.flags & StaticMask;

treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}

completedWork.treeBaseDuration = treeBaseDuration;
} else {
let child = completedWork.child;
// ! 遍历completedWork的所有子节点
while (child !== null) {
// ! 1. 将他们的lanes和childLanes合并到newChildLanes中
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

// “静态”标志(Static flags)与它们所属的 Fiber 或 Hook 共享生命周期,因此即使在放弃(bailout)时,也应该将这些flags向上冒泡。
// 而其他所有flags仅在单次render + commit 的生命周期内存在,因此我们应该忽略它们。
// ! 2. 将他们的(subtreeFlags&StaticMask)和flags合并到subtreeFlags中
subtreeFlags |= child.subtreeFlags & StaticMask;
subtreeFlags |= child.flags & StaticMask;

// 更新return pointer以保持树的一致性。这被描述为一种代码异味(code smell),因为它假设commit阶段永远不会与 render 阶段并发。
// 在重构为交替模型(alternate model)时将会解决这个问题。
child.return = completedWork;
child = child.sibling;
}
}

completedWork.subtreeFlags |= subtreeFlags;
}
// ! 后代节点的lanes
completedWork.childLanes = newChildLanes;

return didBailout;
}
1.5.2.3 createInstance, 创建 DOM 节点,并添加到父节点中

如果组件是HostComponent,即原生标签

1
2
3
4
5
6
7
8
9
10
11
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 把DOM子节点添加到父DOM节点中去
appendAllChildren(instance, workInProgress, false, false);
// 当节点是原生标签,比如div、span等,会在这里添加到fiber属性stateNode上
workInProgress.stateNode = instance;
总结执行流程图

alt text

1.6 重置 workInProgressX

这里没有重置 workInProgress,因为 workInProgress 已经在 performUnitOfWork 阶段更新。

1
2
3
4
5
6
7
8
9
10
// ! 5. 重置
// 重置Context的相关值,如currentlyRenderingFiber等
resetContextDependencies();
// 重置 executionContext
executionContext = prevExecutionContext;

// Set this to null to indicate there's no in-progress render.
// 设置为null,表示没有进行中的render了
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;

1.7 再遍历一遍更新队列

1
finishQueueingConcurrentUpdates()

管理更新队列 finishQueueingConcurrentUpdates

finishQueueingConcurrentUpdatesconcurrentQueues 的内容添加到 fiberqueue 中,后续才是根据 VDOM 更新 DOM

render 阶段,有两处调⽤ finishQueueingConcurrentUpdates ,分别是

  1. render 开始的时候,在 prepareFreshStack 函数中;
    alt text
  2. render 结束的时候,最后再调⽤⼀遍。
    alt text

packages\react-reconciler\src\ReactFiberConcurrentUpdates.js

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
export function finishQueueingConcurrentUpdates(): void {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0; // 重置

concurrentlyUpdatedLanes = NoLanes; // 重置

let i = 0;
while (i < endIndex) {
const fiber: Fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
const queue: ConcurrentQueue = concurrentQueues[i];
concurrentQueues[i++] = null;
const update: ConcurrentUpdate = concurrentQueues[i];
concurrentQueues[i++] = null;
const lane: Lane = concurrentQueues[i];
concurrentQueues[i++] = null;

// 注意:这里构建完之后的fiber.updateQueue.shared.pending数据类型是Update,但是其实这里构建成了一个单向循环链表。
// 所以fiber.updateQueue.shared.pending其实是指最后一个update,它的next指向的是第一个update
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}

if (lane !== NoLane) {
// 更新fiber.lanes
// 从当前节点开始,往上找到根节点,更新childLanes
markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
}

markUpdateLaneFromFiberToRoot

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
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
update: ConcurrentUpdate | null,
lane: Lane,
): void {
// Update the source fiber's lanes
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
// Walk the parent path to the root and update the child lanes.
// 从当前节点开始,往上找到根节点,更新childLanes
let isHidden = false;
let parent = sourceFiber.return;
let node = sourceFiber;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}

if (parent.tag === OffscreenComponent) {
const offscreenInstance: OffscreenInstance | null = parent.stateNode;
if (
offscreenInstance !== null &&
!(offscreenInstance._visibility & OffscreenVisible)
) {
isHidden = true;
}
}

node = parent;
parent = parent.return;
}
}

处理更新队列 processUpdateQueue

这个函数⽤来处理更新队列。

processUpdateQueuebeginWork 阶段会被两个地⽅调⽤:

  1. updateHostRoot packages\react-reconciler\src\ReactFiberBeginWork.js

    1
    processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  2. updateClassInstance packages\react-reconciler\src\ReactFiberClassComponent.js

    在类组件的 mount、resumeMount、更新阶段,均会调⽤。

    1
    processUpdateQueue(workInProgress, newProps, instance, renderLanes);

processUpdateQueue 流程

  1. 检查是否有 pending update。

    1. 如果有,将它们转移到 baseQueue。
    2. pending update 是个单向循环链表,转移到 单链表 firstBaseUpdate->…->lastBaseUpdate 中去。
  2. 遍历 queue,根据这些 update 计算出最后的结果

1
2
3
4
5
6
7
8
9
// 处理这个更新
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
  1. 更新到 fiber 上
    1
    2
    workInProgress.lanes = newLanes;
    workInProgress.memoizedState = newState; // 更新状态