优先级
前文中说到调度器是根据任务以及对应的优先级在合适的时候执行对应任务,那任务的优先级又是如何确定的呢?
在React中存在多个优先级,在具体使用过程中,他们直接可以进行转化:
事件优先级
用户交互过程中产生的事件的优先级
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
// 离散事件 如点击等
export const DiscreteEventPriority: EventPriority = SyncLane;
// 连续事件 如拖拽等
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认事件
export const DefaultEventPriority: EventPriority = DefaultLane;
// 空闲情况下的优先级
export const IdleEventPriority: EventPriority = IdleLane
具体的事件优先级和DOM事件类型的对应关系可以查看源码中的getEventPriority函数
Lane优先级
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000;
export const SomeRetryLane: Lane = RetryLane1;
export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111;
export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
32位的二进制变量 lane 优先级对应的值越小越优先
scheduler优先级
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
优先级转化
由于React中不同的功能模块被处理成独立的包,导致各个包之间的优先级并不通用,在不同阶段需要进行转化,调度过程中React的优先级会经过如下转换:
Lanes优先级 --> 事件优先级 --> scheduler优先级
来看一下具体的转换过程:
Lanes优先级 --> 事件优先级
转换过程由lanesToEventPriority函数进行, 在ensureRootIsScheduled函数中进行调用
export function lanesToEventPriority(lanes: Lanes): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}
函数中首先会拿到最高优先级的lane ,然后逐级和事件优先级的大小进行比较,最后获得lane所属的正确的事件优先级
事件优先级 --> scheduler优先级
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
优先级的运作过程
上述优先级转换函数都是在ensureRootIsScheduled函数中被调用,该函数在每一次用户触发更新和每一次被调度的任务完成后的情况下触发。
来看一下函数中和任务调度有关的部分:
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// 上一次调度的任务
const existingCallbackNode = root.callbackNode;
// 处理饥饿问题
markStarvedLanesAsExpired(root, currentTime);
// 拿到下一个需要处理的优先级
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 任务已经执行完
if (nextLanes === NoLanes) {
// Special case: There's nothing to work on.
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
// 拿到下次需要处理的事件优先级
const newCallbackPriority = getHighestPriorityLane(nextLanes);
// 上一次优先级
const existingCallbackPriority = root.callbackPriority;
// 优先级相同
if (
existingCallbackPriority === newCallbackPriority &&
!(
__DEV__ &&
ReactCurrentActQueue.current !== null &&
existingCallbackNode !== fakeActCallbackNode
)
) {
return;
}
// 存在先前优先级
if (existingCallbackNode != null) {
cancelCallback(existingCallbackNode);
}
//定义新的回调
let newCallbackNode;
// 同步渲染模式
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy !== null) {
ReactCurrentActQueue.didScheduleLegacyUpdate = true;
}
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
if (supportsMicrotasks) {
if (__DEV__ && ReactCurrentActQueue.current !== null) {
ReactCurrentActQueue.current.push(flushSyncCallbacks);
} else {
scheduleMicrotask(() => {
if (
(executionContext & (RenderContext | CommitContext)) ===
NoContext
) {
flushSyncCallbacks();
}
});
}
} else {
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
newCallbackNode = null;
} else {
// 优先级转换
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
// 记录本地的优先级和回调
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
函数流程主要如下:
- 拿到上一次的回调函数
- 使用
markStarvedLanesAsExpired处理饥饿问题 - 调用
getNextLanes获得下一个需要处理的优先级,如果获得的nextLane是null则证明task已经全部处理完成不需要进行新的调度 - 在获取到下一个优先级的情况下, 根据渲染模式进行区别处理最终进入到调度器
scheduler中处理任务执行,得到回调函数
存在不同优先级的任务时不同情况
1.任务执行过程中插入同级任务或者低优先级任务时:
if (
existingCallbackPriority === newCallbackPriority &&
!(
__DEV__ &&
ReactCurrentActQueue.current !== null &&
existingCallbackNode !== fakeActCallbackNode
)
) {
return;
}
通过getNextLanes获取到的优先级仍然是当前优先级, 从而return 不再对调度任务进行调整
3.低优先级任务执行过程中需要插入高优先级任务时
进入cancelCallback清除当前任务回调
if (existingCallbackNode != null) {
cancelCallback(existingCallbackNode);
}
具体的清除逻辑在scheduler中, 主要为将task的回调置为null
function unstable_cancelCallback(task) {
if (enableProfiling) {
if (task.isQueued) {
const currentTime = getCurrentTime();
markTaskCanceled(task, currentTime);
task.isQueued = false;
}
}
task.callback = null;
}
这样在workLoop中将不会继续执行该任务,将任务清除出taskQueue,然后使用scheduleCallback调度当前更高优先级的任务,具体的逻辑在unstable_scheduleCallback中,流程在先前调度器的文章React源码解析 (六) —— 调度器scheduler中已经涉及到不再重复。
当该高优先级任务执行完成后,我们需要继续执行之前停止的任务,此时scheduleCallback注册的回调会触发,根据渲染模式是同步还是异步进入到performConcurrentWorkOnRoot(异步),performSyncWorkOnRoot(同步)中, 在两个函数中最后都会执行如下代码,确保
ensureRootIsScheduled(root, now());
再一次调用getNextLanes取到当前最高优先级的任务,进行处理,此时之前中断的低优先级任务执行
3.饥饿问题处理
当不停的有高优先级任务插入时, 低优先级任务会一直被挂起,为了避免这一问题,使用markStarvedLanesAsExpired进行处理
export function markStarvedLanesAsExpired(
root: FiberRoot,
currentTime: number,
): void {
const pendingLanes = root.pendingLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
const expirationTimes = root.expirationTimes;
let lanes = pendingLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
const expirationTime = expirationTimes[index];
if (expirationTime === NoTimestamp) {
if (
(lane & suspendedLanes) === NoLanes ||
(lane & pingedLanes) !== NoLanes
) {
expirationTimes[index] = computeExpirationTime(lane, currentTime);
}
} else if (expirationTime <= currentTime) {
root.expiredLanes |= lane;
}
lanes &= ~lane;
}
}
函数内部遍历pendingLanes中的lane,判断对应的优先级任务的过期时间,如果没有过期时间,则使用computeExpirationTime给它一个过期时间, 如果有过期时间,则检查是否过期,若过期则将其标注在
expiredLanes中。
经过标注之后在performConcurrentWorkOnRoot函数内部,会通过判断expiredLanes中是否有值,从而决定render是使用同步(renderRootSync)还是异步(renderRootConcurrent)进行,若为同步进行则后续任务的执行不再会被打断,直到过期任务执行完成。
总结
文章主要描述了React中的三个不同任务优先级, 以及它们的转换过程。同时对于不同优先级任务存在的情况下,任务调度如何变化的,以及低优先级任务长时间得不到执行的处理方法等进行了阐述。