0%

react18.2类组件加载过程

1. 加载阶段

1.1 updateClassComponent

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

let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);
// 类组件节点的stateNode 不是存储dom元素,而是组件实例 【hostComponent的stateNode才是dom元素】
const instance = workInProgress.stateNode;
let shouldUpdate;
# 第一次class组件加载,instance都为null
if (instance === null) {
// 初始化构建组件实例
constructClassInstance(workInProgress, Component, nextProps);
// 加载组件实例
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes,
);
} else {
// update阶段:判断是否更新class组件
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}

# 最后:创建class组件child
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
# 返回child子节点
return nextUnitOfWork;
}

updateClassComponent 方法的内容并不复杂,主要就是两个逻辑的执行:

  1. class组件实例的创建或者更新。
  2. 创建组件的child子节点,最后返回子节点。

1.2 构建class组件实例 - constructClassInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
): any {

# 创建class实例对象,参数为props和context
let instance = new ctor(props, context);

// 将instance实例对象的state数据同步到Fiber节点的memoizedState属性
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);

# 确定class组件实例:即链接FiberNode与对应的组件实例
adoptClassInstance(workInProgress, instance);

// dev开发环境下的警告:如果class组件使用了过时的生命周期钩子,发出对应的警告
...

# 返回创建完成的实例
return instance;
}

1.2.1 adoptClassInstance

1
2
3
4
5
6
7
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
// Fiber节点存储instance实例
workInProgress.stateNode = instance;
// instance对象定义一个_reactInternal内部属性存储Fiber节点
setInstance(instance, workInProgress);
}

1.3 加载组件 - mountClassInstance

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 mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes,
): void {

// 取出class组件实例
const instance = workInProgress.stateNode;
// props
instance.props = newProps;
// 同步state数据
instance.state = workInProgress.memoizedState;
// ref:默认为空对象
instance.refs = emptyRefsObject;
// 初始化一个Fiber节点的更新队列
# 设置更新队列对象:fiber.updateQueue = queue;
initializeUpdateQueue(workInProgress);

// 同步组件实例的state数据
instance.state = workInProgress.memoizedState;

const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
// 调用getDerivedStateFromProps钩子
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
instance.state = workInProgress.memoizedState;
}

// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
// 不应该使用旧的生命周期钩子
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
# 触发WillMount生命周期钩子
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
}

# 如果设置了class组件的componentDidMount生命周期钩子函数,则需要在组件的FiberNode上设置对应的flags
if (typeof instance.componentDidMount === 'function') {
let fiberFlags: Flags = Update;
if (enableSuspenseLayoutEffectSemantics) {
fiberFlags |= LayoutStatic;
}

workInProgress.flags |= fiberFlags;
}
}

首先更新了 instance 组件实例上的一些属性,然后初始化了当前组件 Fiber 节点的 updateQueue 属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export function initializeUpdateQueue<State>(fiber: Fiber): void {
// 创建一个更新队列对象
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState, // 初始state数据
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null, // 存储update链表
lanes: NoLanes,
},
effects: null,
};

// 设置updateQueue属性
fiber.updateQueue = queue;
}

然后就是针对 componentWillMount 生命周期钩子函数的处理,虽然是过时的API,但是如果定义了还是需要在这里触发。

最后如果定义了 componentDidMount 钩子函数,则需要给该 Fiber 节点的 flags 属性设置对应的副作用标记。

flags 标记的作用是在 commit 阶段执行对应的副作用操作,所以 componentDidMount 钩子函数会在 commit 阶段中进行触发调用。

到此,类组件的加载就完成了。

2. 更新阶段

1
2
3
4
5
# setState原理
Component.prototype.setState = function(partialState, callback) {
// 调用updater中的一个方法
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

2.1 enqueueSetState

packages/react-reconciler/src/ReactFiberClassComponent.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
const classComponentUpdater = {
isMounted,
enqueueSetState(inst: any, payload: any, callback) {
// ! 1. 获取current和lane
const fiber = getInstance(inst);
const lane = requestUpdateLane(fiber);

// ! 2. 创建update
const update = createUpdate(lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
// ! 3. update入队fiber.updateQueue中, 并返回应用的root根节点
const root = enqueueUpdate(fiber, update, lane);
if (root !== null) {
// ! 4. 调度更新(开启一个从root根节点开始的更新调度)
scheduleUpdateOnFiber(root, fiber, lane);
// ! 5. 处理transitions,非紧急更新
entangleTransitions(root, fiber, lane);
}

if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane);
}
},
enqueueReplaceState(inst: any, payload: any, callback: null) {
// ...
},
enqueueForceUpdate(inst: any, callback) {
// ...
},
};

2.1.1 enqueueUpdate

1
2
3
4
5
6
7
8
9
10
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
): FiberRoot | null {
const updateQueue = fiber.updateQueue;
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;

return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
}

2.1.2 enqueueConcurrentClassUpdate

1
2
3
4
5
6
7
8
9
10
11
12
13
function enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane) {
const interleaved = sharedQueue.interleaved;
// 第一个update对象入队
if (interleaved === null) {
update.next = update;
pushConcurrentUpdateQueue(sharedQueue);
} else {
// 其他的update对象入队
update.next = interleaved.next;
interleaved.next = update;
}
sharedQueue.interleaved = update;
}

回到 enqueueSetState 方法中,这个方法最后会调用 scheduleUpdateOnFiber 函数进入更新的调度程序。

2.2 performSyncWorkOnRoot

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
export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}

const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
// If passive effects were flushed, exit to the outer work loop in the root
// scheduler, so we can recompute the priority.
// TODO: We don't actually need this `ensureRootIsScheduled` call because
// this path is only reachable if the root is already part of the schedule.
// I'm including it only for consistency with the other exit points from
// this function. Can address in a subsequent refactor.
ensureRootIsScheduled(root);
return null;
}

if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
syncNestedUpdateFlag();
}

// * setState
// !1. render阶段
let exitStatus = renderRootSync(root, lanes);
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
// If something threw an error, try rendering one more time. We'll render
// synchronously to block concurrent data mutations, and we'll includes
// all pending updates are included. If it still fails after the second
// attempt, we'll give up and commit the resulting tree.
const originallyAttemptedLanes = lanes;
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes,
);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
errorRetryLanes,
);
}
}

if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes, NoLane);
ensureRootIsScheduled(root);
throw fatalError;
}

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, workInProgressDeferredLane);
ensureRootIsScheduled(root);
return null;
}

// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended.
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// ! 2. commit阶段
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressRootDidIncludeRecursiveRenderUpdate,
workInProgressDeferredLane,
);

// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root);

return null;
}

2.3 updateClassInstance

packages/react-reconciler/src/ReactFiberClassComponent.js

1
2
3
4
5
6
7
8
9
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes,
): boolean {
// ...
}

updateClassInstance 方法里面的内容比较多,我们分成以下几个部分来分析

2.3.1 根据updateQueue计算state

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 instance = workInProgress.stateNode;
# 从旧节点上克隆updateQueue信息,包含了shared.pending中等待处理的更新操作
cloneUpdateQueue(current, workInProgress);
// 旧的props
const unresolvedOldProps = workInProgress.memoizedProps;
const oldProps =
workInProgress.type === workInProgress.elementType
? unresolvedOldProps
: resolveDefaultProps(workInProgress.type, unresolvedOldProps);
instance.props = oldProps;
// 新的props
const unresolvedNewProps = workInProgress.pendingProps;
...

const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
typeof getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function';

// 取出旧的数据
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
# 更新组件实例的数据
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;

首先处理之前的 updateQueue 信息:

这里就是将 current 节点中的 updateQueue 信息克隆到 workInProgress 节点中对应的属性中。
注意:react应用每次更新都会执行Fiber Reconciler流程【即FiberTree的创建流程】,在进入此流程之前,current节点会存储本次更新相关的一些信息,但是在进入此流程之后,current就变成了旧的节点,workInProgress代表新建的节点,此时就需要将current节点上的一些信息保存到新的节点之中,也就是对应的workInProgress。

从class类中取出 getDerivedStateFromProps 钩子,判断当前类组件有没有使用此钩子,如果没有使用则设置变量 hasNewLifecyclesfalse,此变量的作用是后续判断其他生命周期钩子的执行与否。

下面开始对state的处理:

  1. 首先取出旧的state数据:
  2. 重点:调用processUpdateQueue方法,根据updateQueue信息,计算生成新的state数据。
2.3.1.1 processUpdateQueue

packages/react-reconciler/src/ReactFiberClassUpdateQueue.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
export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {

didReadFromEntangledAsyncAction = false;

// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);

hasForceUpdate = false;

let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;

// Check if there are pending updates. If so, transfer them to the base queue.
// *1. 检查是否有pending update。如果有,需要将它们剪开环型链表,合并到 baseQueue。
// pending update是个单向循环链表,转移到 单链表 firstBaseUpdate->...->lastBaseUpdate 中去
// ! 这里注意pending update不同于baseQueue,pending update只记录了尾节点
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
// ? sy
queue.shared.pending = null;

// The pending queue is circular. Disconnect the pointer between first
// and last so that it's non-circular.
// ! 这里的 pendingQueue 是在 finishQueueingConcurrentUpdates 函数中构建的单向循环链表
const lastPendingUpdate = pendingQueue; // 尾节点
const firstPendingUpdate = lastPendingUpdate.next; // 头结点
lastPendingUpdate.next = null; // 断开循环链表
// Append pending updates to base queue
// 把pending update转移到base queue上
// ! 接下来构建单链表 firstBaseUpdate->...->lastBaseUpdate
if (lastBaseUpdate === null) {
// base queue是空的,那么firstPendingUpdate就是头节点firstBaseUpdate
firstBaseUpdate = firstPendingUpdate;
} else {
// 否则往这个尾节点后继续加
lastBaseUpdate.next = firstPendingUpdate;
}
// 更新尾节点, 将 lastBaseUpdate 赋值为 lastPendingUpdate
// 此时已经形成了 以 firstBaseUpdate 为头以 lastBaseUpdate 为尾的新链表
// 也即为本次需要处理的 update 链表
lastBaseUpdate = lastPendingUpdate;

// If there's a current queue, and it's different from the base queue, then
// we need to transfer the updates to that queue, too. Because the base
// queue is a singly-linked list with no cycles, we can append to both
// lists and take advantage of structural sharing.
// TODO: Pass `current` as argument
const current = workInProgress.alternate;
// 如果有current queue,并且它和base queue不同,那么我们也需要把更新转移到那个queue上。
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
// 类组件和 HostRoot 的updateQueue都初始化过,所以这里不会是null
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
// 如果current的lastBaseUpdate和baseQueue的lastBaseUpdate不同,那么把pending update转移到currentQueue上
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}

// These values may change as we process the queue.
if (firstBaseUpdate !== null) {
// *2. 接下来要做的就是遍历queue,然后根据这些update,计算出最后的结果。
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
// TODO: Don't need to accumulate this. Instead, we can remove renderLanes
// from the original lanes.
let newLanes = NoLanes;
// 这里的 newBaseState, newFirstBaseUpdate,newLastBaseUpdate 是计算的临时变量
// 实际上会用来更新 updateQueue 的 baseState, firstBaseUpdate, lastBaseUpdate
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate: null | Update<State> = null;

let update: Update<State> = firstBaseUpdate;
do {
// An extra OffscreenLane bit is added to updates that were made to
// a hidden tree, so that we can distinguish them from updates that were
// already there when the tree was hidden.
const updateLane = removeLanes(update.lane, OffscreenLane);
const isHiddenUpdate = updateLane !== update.lane;

// Check if this update was made while the tree was hidden. If so, then
// it's not a "base" update and we should disregard the extra base lanes
// that were added to renderLanes when we entered the Offscreen tree.
const shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);

// 更新优先级不满足,该 update 会被跳过
if (shouldSkipUpdate) {
// ? sy-no
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<State> = {
lane: updateLane,

tag: update.tag,
payload: update.payload,
callback: update.callback,

next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priority in the queue.
newLanes = mergeLanes(newLanes, updateLane);
} else {
// 该 update 更新优先级满足,本次更新不会跳过
// This update does have sufficient priority.

// Check if this update is part of a pending async action. If so,
// we'll need to suspend until the action has finished, so that it's
// batched together with future updates in the same action.

// 请检查此更新是否属于待处理的异步操作。如果是的话,
// 我们需要暂停直到操作完成,以便将其与将来的更新一起批处理在同一操作中。
if (updateLane !== NoLane && updateLane === peekEntangledActionLane()) {
didReadFromEntangledAsyncAction = true;
}

// 如果 newLastBaseUpdate 不存在,说明之前没有跳过任何 upadte 无需添加新增
// 否则无论无论该 update 是否跳过都需要添加到 baseUpdate 链表之后
if (newLastBaseUpdate !== null) {
// ? sy-no
const clone: Update<State> = {
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,

tag: update.tag,
payload: update.payload,

// When this update is rebased, we should not fire its
// callback again.
callback: null,

next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}

// Process this update.
// 处理这个更新
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
// 类组件的setState会在这里存储
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback;
if (isHiddenUpdate) {
workInProgress.flags |= Visibility;
}
const callbacks = queue.callbacks;
if (callbacks === null) {
queue.callbacks = [callback];
} else {
callbacks.push(callback);
}
}
}
// $FlowFixMe[incompatible-type] we bail out when we get a null
// 下一个update
update = update.next;
if (update === null) {
// 已经到达尾节点,所有update被处理完毕,暂停循环
pendingQueue = queue.shared.pending;
// 这里与函数组件有所不同,当setState套setState时候,虽然这种情况都会加入到.pending中
// 但是class组件会在最后判断,如有就继续执行
// 而函数组件添加时dispatchSetState会走enqueueRenderPhaseUpdate添加任务,不会走scheduleUpdateOnFiber这个分支,在renderWithHooks中Component()执行结束后会走renderWithHooksAgain,再次执行一遍组件
// this.setState((prevState, props) => {
// this.setState({ count: 100 })
// return {
// count: prevState.count + 1,
// }
// })
if (pendingQueue === null) {
break;
} else {
// An update was scheduled from inside a reducer. Add the new
// pending updates to the end of the list and keep processing.
// 已经计划在 reducer 内部进行更新。将新的待处理更新添加到列表末尾并继续处理。
const lastPendingUpdate = pendingQueue;
// Intentionally unsound. Pending updates form a circular list, but we
// unravel them when transferring them to the base queue.
const firstPendingUpdate =
((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);

if (newLastBaseUpdate === null) {
newBaseState = newState;
}

// 更新 updateQueue 的 baseState,firstBaseUpdate, lastBaseUpdate 三个属性
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;


if (firstBaseUpdate === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
// 当多个transitions在同一个queue中时,只允许最近的一个完成。我们不应该显示中间状态。
// 当queue为空,我们将queue.lanes设置回0
queue.shared.lanes = NoLanes;
}

// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
// 把跳过的update的lane记录下来
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState; // 更新状态
}
}

2.3.2 调用getDerivedStateFromProps钩子

1
2
3
4
5
6
7
8
9
10
if (typeof getDerivedStateFromProps === 'function') {
// 调用getDerivedStateFromProps钩子
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
newState = workInProgress.memoizedState;
}

如果类组件定义了 getDerivedStateFromProps 钩子函数,则在此触发此回调。

2.3.3 调用shouldComponentUpdate钩子,检查组件是否应该更新

1
2
3
4
5
const shouldUpdate =
// 检查是否为强制更新
checkHasForceUpdateAfterProcessing() ||
// 调用shouldComponentUpdate钩子,检查是否应该更新
checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext)
2.3.3.1 checkHasForceUpdateAfterProcessing

checkHasForceUpdateAfterProcessing 方法返回一个变量的状态:

1
2
3
export function checkHasForceUpdateAfterProcessing(): boolean {
return hasForceUpdate;
}

hasForceUpdate 是一个全局变量,它默认为 false ,表示非强制更新,它的修改就在之前计算state的逻辑中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
newState = getStateFromUpdate(...)


function getStateFromUpdate<State>() {
switch (update.tag) {
case ReplaceState: {}
case CaptureUpdate: {}
case UpdateState: {}
// 强制更新 场景
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
}

如果为 this.forceUpdate 触发的场景,就会进入 ForceUpdate 分支,更新变量 hasForceUpdate 的值为 true

这时在校验组件是否应该更新时,就会返回 true,代表组件需要更新。

而我们当前是通过 this.setState 修改数据触发的更新,所以当前 hasForceUpdate 是为 false 的,这也是绝大部分类组件更新的场景。

2.3.3.2 checkShouldComponentUpdate

查看 checkShouldComponentUpdate

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 checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
let shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
return shouldUpdate;
}

// 针对PureComponent纯组件的 内部校验
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}

return true;
}

checkShouldComponentUpdate 方法也比较简单,主要就是调用一次我们定义的 shouldComponentUpdate 钩子,根据调用的返回值来决定类组件是否应该更新,如果我们没有使用 shouldComponentUpdate 钩子,则类组件是默认需要更新的。

同时在这里我们还可以发现有一个针对 PureComponent 纯组件的更新校验,这其实就是 PureComponentComponent 唯一的区别,纯组件 PureComponent在react内部自动帮助我们对 propsstate 进行了浅比较,任何一个变化则返回 true,需要更新组件。

2.3.4 为组件Fiber的flags属性设置componentDidUpdate和getSnapshotBeforeUpdate副作用标记

1
2
3
4
5
6
7
8
9
10
11
# 执行componentWillUpdate钩子
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, nextContext);
}
// 更新flags标记
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.flags |= Update;
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
workInProgress.flags |= Snapshot;
}

如果类组件定义了 componentWillUpdate 生命周期钩子函数,则会在此处调用此函数。

然后如果类组件定义了 componentDidUpdate 或者 getSnapshotBeforeUpdate 生命周期钩子函数,就会更新组件 Fiberflags 副作用标记。

这些生命周期钩子会在commit阶段,真实DOM渲染完成之后,被触发执行。

最后同步更新组件实例instance的props和state:

1
2
instance.props = newProps;
instance.state = newState;

到此,一个类组件的更新程序基本执行完成。

2.4 commit阶段

前面全部的加载逻辑都是在Fiber Reconciler协调流程中执行的,即类组件大部分的加载或者更新逻辑都是在reconciler协调流程中完成的,还有剩下的一部分逻辑在commit阶段之中处理。

对于类组件的更新来说,在commit阶段主要还有以下两部分逻辑需要处理:

  1. 执行类组件的 componentDidUpdate 生命周期钩子函数。
  2. 执行 this.setState 方法传入的回调函数。

packages/react-reconciler/src/ReactFiberCommitWork.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
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
// 根据组件类型:进行不同的处理
switch (finishedWork.tag) {

// 类组件的处理
case ClassComponent: {
// 组件实例
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (!offscreenSubtreeWasHidden) {
if (current === null) {
// mount加载阶段

# 触发componentDidMount 生命周期钩子函数【这类静态方法:是存储在instance对象原型上的】
instance.componentDidMount();
} else {

// update更新阶段
const prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps
: resolveDefaultProps( finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
# 触发componentDidUpdate 生命周期钩子函数
instance.componentDidUpdate( prevProps,prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
}

# 取出当前组件节点的updateQueue更新对象
const updateQueue: UpdateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
// 触发更新
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}

...
}
}
}

finishedWork代表当前处理的类组件Fiber节点,首先从fiber.stateNode属性中取出组件实例instance。然后根据进行判断,只有存在相关的副作用标记才会继续内部的逻辑:

然后判断current是否为null,current表示旧的虚拟DOM节点,在组件的更新阶段,它肯定是存在的。然后直接调用 componentDidUpdate 生命周期钩子函数即可。

3. 总结

类组件的更新逻辑主要内容是以下两点:

  1. reconciler 协调流程中循环 update 链表计算出最新的 state
  2. commit 阶段中触发 componentDidUpdate 生命周期钩子函数以及循环执行 this.setState 的回调。