0%

react18.2源码分析(二)scheduleUpdateOnFiber调度更新

react源码分析(二)scheduleUpdateOnFiber调度更新

scheduleUpdateOnFiber 函数主要是触发一个在 Fiber 节点上的调度更新任务,⻚⾯初次渲染、类组件 setState/forceUpdate、函数组件 setState 都会⾛到更新,都会调⽤ scheduleUpdateOnFiber 函数。

如果以下源码没有特殊标记路径,那么路径都和 scheduleUpdateOnFiber ⼀样,即 packages/react-reconciler/src/ReactFiberWorkLoop.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
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
) {
// ...

// 1. 标记根节点有一个 pending update,即待处理的更新。
markRootUpdated(root, lane);

if (
(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot
) {
// 这个更新是在渲染阶段期间发送的。
// 如果更新源自用户空间(除了本地hook update之外,hook update会被单独处理,不会到达这个函数),那么这是一个错误。
// 但是,有一些内部的 React 功能将这个作为实现细节,比如选择性hydration。
warnAboutRenderPhaseUpdatesInDEV(fiber);

// Track lanes that were updated during the render phase
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {

// ...

// 页面初次渲染时候,此时workInProgressRoot还是null
if (root === workInProgressRoot) {
// Received an update to a tree that's in the middle of rendering. Mark
// that there was an interleaved update work on this root.
if ((executionContext & RenderContext) === NoContext) {
workInProgressRootInterleavedUpdatedLanes = mergeLanes(
workInProgressRootInterleavedUpdatedLanes,
lane,
);
}
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
// TODO: Make sure this doesn't override pings that happen while we've
// already started rendering.
markRootSuspended(
root,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
);
}
}

// 2. 开始调度
ensureRootIsScheduled(root);

if (
lane === SyncLane &&
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode
) {
if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy) {
// Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
} else {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
resetRenderTimer();
flushSyncWorkOnLegacyRootsOnly();
}
}
}
}

1. markRootUpdated

标记根节点有一个 pending update,即待处理的更新。

1
markRootUpdated(root, lane);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function markRootUpdated(root: FiberRoot, updatedLanes: Lanes) {
_markRootUpdated(root, updatedLanes);

if (enableInfiniteRenderLoopDetection) {
// ? sy
// Check for recursive updates
if (executionContext & RenderContext) {
workInProgressRootDidIncludeRecursiveRenderUpdate = true;
} else if (executionContext & CommitContext) {
didIncludeCommitPhaseUpdate = true;
}
// getRootForUpdatedFiber中也有这个检测
// 如果循环超过限制次数(50次),抛出错误。比如在类组件的render函数里执行setState
throwIfInfiniteUpdateLoopDetected();
}

_markRootUpdated 来自 packages\react-reconciler\src\ReactFiberLane.js 的 markRootUpdated

1
2
3
4
5
6
7
8
9
10
export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
// 设置本次更新的优先级, 标记其有正在等待更新的任务,此时挂载的优先级将会后在后续被转换为调度优先级,方便调度程序的使用。
root.pendingLanes |= updateLane;

// 如果update是idle的,将不会处理它,因为我们直到所有常规update完成后才会处理idle更新;
if (updateLane !== IdleLane) {
root.suspendedLanes = NoLanes; //【由于Suspense而挂起的update对应的lane集合】
root.pingedLanes = NoLanes; // 【由于请求成功,Suspense取消挂起的update对应的Lane集合】
}
}

markRootUpdated 主要做的就是设置本次更新的优先级,标记其有正在等待更新的任务,此时挂载的优先级将会后在后续被转换为调度优先级,方便调度程序的使用。

2. ensureRootIsScheduled(root)

每次 root: FiberRoot 接收 update 的时候,这个函数都会被调⽤。

  1. 确保 root 在 root 调度中
  2. 确保有⼀个待处理的微任务来处理根调度

packages\react-reconciler\src\ReactFiberRootScheduler.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
// 单链表
// 一般应用中,只有一个根节点,但是React支持多个根节点。
let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;
// 用于没有同步work的时候,快速退出flushSync
let mightHavePendingSyncWork: boolean = false;

export function ensureRootIsScheduled(root: FiberRoot): void {
// 把root添加到调度中
if (root === lastScheduledRoot || root.next !== null) {
// ? 后两次
// Fast path. This root is already scheduled.
// 已经添加到了调度中
} else {
// ? sy 前一次
if (lastScheduledRoot === null) {
// ? sy
firstScheduledRoot = lastScheduledRoot = root;
} else {
// 多个根节点
lastScheduledRoot.next = root;
lastScheduledRoot = root;
}
}

// 每当root接收到update,我们将其设置为true,直到下次处理调度为止。如果为false,那么我们可以在不查看调度的情况下快速退出 flushSync。
mightHavePendingSyncWork = true;

// 在当前事件结束时,逐个检查每个root,并确保为每个root安排了正确优先级的任务。
if (__DEV__ && ReactCurrentActQueue.current !== null) {
// ...
} else {
if (!didScheduleMicrotask) {
// ? sy
didScheduleMicrotask = true;
scheduleImmediateTask(processRootScheduleInMicrotask);
}
}

if (!enableDeferRootSchedulingToMicrotask) {
// While this flag is disabled, we schedule the render task immediately
// instead of waiting a microtask.
// TODO: We need to land enableDeferRootSchedulingToMicrotask ASAP to
// unblock additional features we have planned.
scheduleTaskForRootDuringMicrotask(root, now());
}
}

2.1 scheduleImmediateTask

packages\react-reconciler\src\ReactFiberRootScheduler.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
function scheduleImmediateTask(cb: () => mixed) {
// TODO: Can we land supportsMicrotasks? Which environments don't support it?
// Alternatively, can we move this check to the host config?
if (supportsMicrotasks) {
// sy-
scheduleMicrotask(() => {
// In Safari, appending an iframe forces microtasks to run.
// 在Safari中,附加一个iframe会强制微任务运行。
// https://github.com/facebook/react/issues/22459
// We don't support running callbacks in the middle of render
// or commit so we need to check against that.
// 在渲染或提交过程中我们不支持运行回调函数,因此我们需要进行检查。
const executionContext = getExecutionContext();
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
// sy-no
// Note that this would still prematurely flush the callbacks
// if this happens outside render or commit phase (e.g. in an event).
// 如果这种情况发生在render或commit阶段之外(例如在事件中),这仍然会过早地刷新回调函数。

// Intentionally using a macrotask instead of a microtask here. This is
// wrong semantically but it prevents an infinite loop. The bug is
// Safari's, not ours, so we just do our best to not crash even though
// the behavior isn't completely correct.
// 在这里故意使用宏任务而不是微任务。这在语义上是错误的,但它可以防止无限循环。
// 这个 bug 是 Safari 的问题,而不是我们的问题,所以我们尽力避免崩溃,即使行为并不完全正确。
Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);
return;
}
cb();
});
} else {
// If microtasks are not supported, use Scheduler.
Scheduler_scheduleCallback(ImmediateSchedulerPriority, cb);
}
}

2.1.1 scheduleMicrotask

packages\react-dom-bindings\src\client\ReactFiberConfigDOM.js

1
2
3
4
5
6
7
export const scheduleMicrotask: any =
typeof queueMicrotask === 'function'
? queueMicrotask
: typeof localPromise !== 'undefined'
? callback =>
localPromise.resolve(null).then(callback).catch(handleErrorInNextTick)
: scheduleTimeout; // TODO: Determine the best fallback here.

2.2 processRootScheduleInMicrotask

packages\react-reconciler\src\ReactFiberRootScheduler.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
function processRootScheduleInMicrotask() {
// 这个函数总是在microtask中被调用,它绝对不应该被同步调用。
didScheduleMicrotask = false;

// 我们将在遍历所有roots并调度它们时重新计算这个
mightHavePendingSyncWork = false;

const currentTime = now();

let prev = null;
let root = firstScheduledRoot;
while (root !== null) {

const next = root.next;

if (
currentEventTransitionLane !== NoLane &&
shouldAttemptEagerTransition()
) {
// A transition was scheduled during an event, but we're going to try to
// render it synchronously anyway. We do this during a popstate event to
// preserve the scroll position of the previous page.
upgradePendingLaneToSync(root, currentEventTransitionLane);
}

const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
if (nextLanes === NoLane) {
// ? sy second
// 页面初次渲染,再执行这里。nextLanes=0
// This root has no more pending work. Remove it from the schedule. To
// guard against subtle reentrancy bugs, this microtask is the only place
// we do this — you can add roots to the schedule whenever, but you can
// only remove them here.

// Null this out so we know it's been removed from the schedule.
//root 没有更多pending work。从调度中移除它。为了防止微妙的重入bug,这个microtask是我们唯一执行此操作的地方 — 你可以随时将roots添加到调度中,但只能在这里将它们移除。
// 将其置null,以便我们知道它已从调度中移除
root.next = null;
if (prev === null) {
// ? sy
// This is the new head of the list
firstScheduledRoot = next;
} else {
prev.next = next;
}
if (next === null) {
// ? sy
// This is the new tail of the list
lastScheduledRoot = prev;
}
} else {
// ? sy first
// 页面初次渲染,先执行这里。nextLanes=32
// This root still has work. Keep it in the list.
prev = root;
if (includesSyncLane(nextLanes)) {
// ? sy-no
mightHavePendingSyncWork = true;
}
}
root = next;
}

currentEventTransitionLane = NoLane;

// At the end of the microtask, flush any pending synchronous work. This has
// to come at the end, because it does actual rendering work that might throw.
// 在microtask结束时,flush任何pending的同步work。这必须放在最后,因为它执行实际的可能会抛出异常的渲染工作。
// onClick count
flushSyncWorkOnAllRoots();
}

2.3 scheduleTaskForRootDuringMicrotask

packages\react-reconciler\src\ReactFiberRootScheduler.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
function scheduleTaskForRootDuringMicrotask(
root: FiberRoot,
currentTime: number,
): Lane {
// 这个函数总是在microtask中被调用,或者在渲染任务的最后,在我们将控制权交还给主线程之前被调用。它绝对不应该被同步调用。

// 检查是否有任何lanes被其他work饿死。如果是,将它们标记为过期,这样我们就知道下一个要处理的是它们。
markStarvedLanesAsExpired(root, currentTime);

// Determine the next lanes to work on, and their priority.
const workInProgressRoot = getWorkInProgressRoot();
const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();

// 基于root.pendingLanes,计算出本次更新的批次Lanes
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);

const existingCallbackNode = root.callbackNode;
// 如果优先级等于0 ,说明根节点没有可处理的回调,则退出任务调度
if (
// Check if there's nothing to work on
nextLanes === NoLanes ||
// If this root is currently suspended and waiting for data to resolve, don't
// schedule a task to render it. We'll either wait for a ping, or wait to
// receive an update.
//
// Suspended render phase
(root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||
// Suspended commit phase
root.cancelPendingCommit !== null
) {
// Fast path: There's nothing to work on.
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return NoLane;
}

// Schedule a new callback in the host environment.
if (includesSyncLane(nextLanes)) {
// sy- setState
// sy-no 初次渲染
// Synchronous work is always flushed at the end of the microtask, so we
// don't need to schedule an additional task.
// 同步工作始终在微任务结束时刷新,因此我们不需要安排额外的任务。
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackPriority = SyncLane;
root.callbackNode = null;
return SyncLane;
} else {
// We use the highest priority lane to represent the priority of the callback.
const existingCallbackPriority = root.callbackPriority;
const newCallbackPriority = getHighestPriorityLane(nextLanes);

if (
newCallbackPriority === existingCallbackPriority &&
// Special case related to `act`. If the currently scheduled task is a
// Scheduler task, rather than an `act` task, cancel it and re-schedule
// on the `act` queue.
!(
__DEV__ &&
ReactCurrentActQueue.current !== null &&
existingCallbackNode !== fakeActCallbackNode
)
) {
// The priority hasn't changed. We can reuse the existing task.
return newCallbackPriority;
} else {
// Cancel the existing callback. We'll schedule a new one below.
cancelCallback(existingCallbackNode);
}

// 定义一个【调度的优先级】,因为react事件优先级和scheduler的优先级不同,需要经过转换【即:事件优先级转换为调度优先级】
let schedulerPriorityLevel;
// lanes转换成事件优先级,匹配符合的优先级,然后赋值对应的scheduler的优先级
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority: // 32
// ? sy 页面初次渲染、transition(128)
schedulerPriorityLevel = NormalSchedulerPriority; // 3
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}

// ? sy 页面初次渲染
const newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);

root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
return newCallbackPriority;
}
}

2.3.1 markStarvedLanesAsExpired

检查是否有lanes挨饿,如果有,则标记他们过期,以便下次执行。详情参考lanes文章。

2.3.2 getWorkInProgressRoot

1
2
3
4
5
let workInProgressRoot: FiberRoot | null = null;

export function getWorkInProgressRoot(): FiberRoot | null {
return workInProgressRoot;
}

2.3.3 getWorkInProgressRootRenderLanes

1
2
3
export function getWorkInProgressRootRenderLanes(): Lanes {
return workInProgressRootRenderLanes;
}

2.3.4 getNextLanes

获取本次更新的lanes。详情参考lanes文章。

2.3.5 lanesToEventPriority

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function lanesToEventPriority(lanes: Lanes): EventPriority {
// 根据优先级最高的lane,返回对应的 EventPriority。这里对应Scheduler包中的优先级
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}

2.3.6 scheduleCallback

进入 schedule 调度器.

1
2
3
4
5
6
function scheduleCallback(
priorityLevel: PriorityLevel,
callback: RenderTaskFn,
) {
return Scheduler_scheduleCallback(priorityLevel, callback);
}

Scheduler_scheduleCallback 最终来自 packages\scheduler\src\forks\Scheduler.js 的 unstable_scheduleCallback,在 react调度器scheduler源码分析 会详细讲到。

2.3.7 performConcurrentWorkOnRoot

具体看 render 阶段文章。