ensureRootIsScheduled方法解析
# 概览
每当根节点接收到更新时,都会调用ensureRootIsScheduled函数,它执行两项操作:
- 确保根节点的更新在调度中
- 确保有一个(挂起的)微任务来处理1中的调度
大部分实际的调度逻辑是在scheduleTaskForRootDuringMicrotask方法运行时才会执行
# 源码解析
# ensureRootIsScheduled
ensureRootIsScheduled方法会根据条件将根节点添加到调度链表,若没有已安排的微任务,则调用scheduleImmediateRootScheduleTask安排新微任务。其源码实现如下:
let firstScheduledRoot = null; // 链表头:第一个待调度的根节点
let lastScheduledRoot = null; // 链表尾:最后一个待调度的根节点
let didScheduleMicrotask = false; // 是否已安排微任务
function ensureRootIsScheduled(root) {
/**
* 将根节点添加到调度链表中
*/
// 判断 若根节点不是最后一个已调度的,且没有在链表中
if (root !== lastScheduledRoot && root.next === null) {
// 若链表为空,则将链表的首尾都设置为当前根节点
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
} else {
// 链表不为空,则添加到末尾
lastScheduledRoot.next = root;
lastScheduledRoot = root;
}
}
// 标记有未完成的同步工作
mightHavePendingSyncWork = true;
// 如果没有已安排的微任务,则安排新的微任务
if (!didScheduleMicrotask) {
didScheduleMicrotask = true;
scheduleImmediateRootScheduleTask();
}
}
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
# scheduleImmediateRootScheduleTask
scheduleImmediateRootScheduleTask顾名思义就是将根调度安排到一个微任务中去执行。
var scheduleCallback$3 = Scheduler.unstable_scheduleCallback;
function scheduleImmediateRootScheduleTask() {
scheduleMicrotask(function () {
// 检查当前是否处于渲染或提交阶段
if ((executionContext & (RenderContext | CommitContext)) !== 0) {
// 若不在这两个阶段,则调用scheduleCallback$3
scheduleCallback$3(ImmediatePriority, processRootScheduleInImmediateTask);
} else {
// 若当前处于渲染或提交阶段,则直接调用processRootScheduleInMicrotask
processRootScheduleInMicrotask();
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
scheduleCallback$3本质上是调度器scheduler的一个方法,会根据优先级进行调度,后面会讲到。ImmediatePriority表示任务的优先级,是一个高优级,即时更新。
# scheduleMicrotask
而 scheduleMicrotask则是将任务(回调)放在微任务队列中执行,其实现如下:
var scheduleTimeout = "function" === typeof setTimeout ? setTimeout : void 0,
cancelTimeout = "function" === typeof clearTimeout ? clearTimeout : void 0,
localPromise = "function" === typeof Promise ? Promise : void 0,
scheduleMicrotask =
"function" === typeof queueMicrotask
? queueMicrotask
: "undefined" !== typeof localPromise
? function (callback) {
return localPromise
.resolve(null)
.then(callback)
.catch(handleErrorInNextTick);
}
: scheduleTimeout;
2
3
4
5
6
7
8
9
10
11
12
13
14
scheduleMicrotask的优先级顺序:
- 优先使用
queueMicrotask(现代浏览器API) - 其次使用
Promise.then(Promise微任务) - 最后回退到
setTimeout(宏任务)
这种设计确保了最佳的微任务调度性能。
# processRootScheduleInMicrotask
processRootScheduleInMicrotask方法总是在微任务中被调用,并且从不被同步调用。
function processRootScheduleInMicrotask() {
// 重置微任务调度标志
didScheduleMicrotask = false;
// 重置待同步任务标志
mightHavePendingSyncWork = false;
let syncTransitionLanes = NoLanes;
// 检查当前是否有事件过渡车道
if (currentEventTransitionLane !== NoLane) {
// 判断是否应该尝试急切过渡,若需要,则设置同步过渡车道
if (shouldAttemptEagerTransition()) {
syncTransitionLanes = currentEventTransitionLane;
}
}
// 链表遍历与调度
for (
var currentTime = now(), prev = null, root = firstScheduledRoot;
null !== root;
) {
// 为每个根节点调用scheduleTaskForRootDuringMicrotask进行调度任务
var next = root.next,
nexLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
// 如果返回的车道是NoLane,则表示没有任务需要调度,就需要更新链表,从链表中移除该节点
if (nexLanes === NoLane) {
root.next = null;
if (prev === null) {
firstScheduledRoot = next;
} else {
prev.next = next;
}
if (next === null) {
lastScheduledRoot = prev;
}
} else {
// 将prev更新为当前根节点
prev = root;
// 若有同步多度车道或者返回的车道中包含高优先级车道,则标记有待处理的同步工作
if (syncTransitionLanes !== NoLanes || NoLanes !== (nexLanes & 3)) {
mightHavePendingSyncWork = true;
}
}
// 更新当前节点,处理下一个节点
root = next;
}
// 如果存在一个正在commit的工作,则跳过同步刷新工作,否则调用flushSyncWorkAcrossRoots_impl筛选所有同步工作
(0 !== pendingEffectsStatus && 5 !== pendingEffectsStatus) ||
flushSyncWorkAcrossRoots_impl(syncTransitionLanes, !1);
// 重置当前事件过渡车道
0 !== currentEventTransitionLane && (currentEventTransitionLane = 0);
}
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
# scheduleTaskForRootDuringMicrotask
scheduleTaskForRootDuringMicrotask方法决定了何时、如何以及以什么优先级执行更新,是React并发渲染能力的关键所在。
function scheduleTaskForRootDuringMicrotask(root, currentTime) {
// 遍历所有待处理车道过期时间
for (
var suspendedLanes = root.suspendedLanes,
pingedLanes = root.pingedLanes,
expirationTimes = root.expirationTimes,
lanes = root.pendingLanes & -62914561;
0 < lanes;
) {
var index$5 = 31 - clz32(lanes),
lane = 1 << index$5,
expirationTime = expirationTimes[index$5];
// 若未设置过期时间
if (-1 === expirationTime) {
// 若车道未被挂起,或者被检测到,则使用当前时间计算一个新的过期时间并赋值给该车道
if (0 === (lane & suspendedLanes) || 0 !== (lane & pingedLanes))
expirationTimes[index$5] = computeExpirationTime(lane, currentTime);
} else expirationTime <= currentTime && (root.expiredLanes |= lane);// 若已过期,则标记为已过期,方便后续处理
// 清除已处理的车道
lanes &= ~lane;
}
// 保存workInProgressRoot的相关引用
currentTime = workInProgressRoot;
suspendedLanes = workInProgressRootRenderLanes;
// 调用getNextLanes获取下一个要处理的车道
suspendedLanes = getNextLanes(
root,
root === currentTime ? suspendedLanes : 0, // 如果当前根节点是正在工作的根节点,则使用其渲染车道
null !== root.cancelPendingCommit || -1 !== root.timeoutHandle, // 是否应该包含同步车道,若根节点有取消提交或超时了,则包含
);
// 获取当前已调度的回调节点
pingedLanes = root.callbackNode;
// 检查是否需要取消调度
/**
* 1. 没有要处理的车道
* 2. 当前根节点是工作根节点并且工作节点被挂起
* 3. 根节点有取消提交
*/
if (
0 === suspendedLanes ||
(root === currentTime &&
(2 === workInProgressSuspendedReason ||
9 === workInProgressSuspendedReason)) ||
null !== root.cancelPendingCommit
){
// 取消已处理的回调
null !== pingedLanes &&cancelCallback$1(pingedLanes);
// 重置回调节点和优先级
(root.callbackNode = null),
(root.callbackPriority = 0)
return NoLanes // 返回0 表示无车道
}
// 检查是否应该调度异步任务:车道中不包含同步车道或者根节点处于预渲染状态
if (
0 === (suspendedLanes & 3) ||
checkIfRootIsPrerendering(root, suspendedLanes)
) {
// 获取最高优先级的车道
currentTime = suspendedLanes & -suspendedLanes;
// 若优先级与当前回调优先级相同,则直接返回最高优先级车道
if (currentTime === root.callbackPriority) return currentTime;
// 取消现有的回调
null !== pingedLanes && cancelCallback$1(pingedLanes);
// 将车道转换为调度器优先级
switch (lanesToEventPriority(suspendedLanes)) {
case 2:
case 8:
suspendedLanes = UserBlockingPriority; //用户阻塞优先级
break;
case 32:
suspendedLanes = NormalPriority$1; // 普通优先级
break;
case 268435456:
suspendedLanes = IdlePriority; // 空闲优先级
break;
default:
suspendedLanes = NormalPriority$1; // 默认为普通优先级
}
// 创建回调函数,该回调函数中会调用 performWorkOnRoot
pingedLanes = performWorkOnRootViaSchedulerTask.bind(null, root);
// 使用调度器scheduler调度任务
suspendedLanes = scheduleCallback$3(suspendedLanes, pingedLanes);
// 更新根节点的回调信息
root.callbackPriority = currentTime;
root.callbackNode = suspendedLanes;
// 返回车道
return currentTime;
}
// 同步任务处理
// 取消现有的回调
null !== pingedLanes && cancelCallback$1(pingedLanes);
// 设置同步优先级
root.callbackPriority = 2;
root.callbackNode = null;
// 返回同步车道
return 2;
}
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
# flushSyncWorkAcrossRoots_impl
flushSyncWorkAcrossRoots_impl方法会筛选同步任务,其伪代码实现如下:
function flushSyncWorkAcrossRoots_impl(){
if(!isFlushingWork && mightHavePendingSyncWork){
isFlushingWork = !0;
do{
var didPerformSomeWork = !1;
for(let root = firstScheduleRoot;root!=null;){
performSyncWorkOnRoot(root, nextLanes);
root = root.next;
}
}while(didPerformSomeWork)
isFlushingWork = !1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
flushSyncWorkAcrossRoots_impl方法会遍历和刷新所有根节点的同步工作,内部的核心函数就是调用performSyncWorkOnRoot.
# 总结
同步更新的入口是performSyncWorkOnRoot,异步更新入口是performWorkOnRoot,而前者本质上还是调用的performWorkRoot,第三个参数为true。
function performSyncWorkOnRoot(root, lanes) {
if (flushPendingEffects()) return null;
performWorkOnRoot(root, lanes, !0);
}
2
3
4