Appearance
任务优先级
在任务调度这一节,我们知道了怎么将一个更新操作转化成对应的 update, 然后根据 mode 去放到各自的任务栈中,并通过定时器、requestHostTimeout 去在一定的时间内将这些任务从对应的任务栈中取出,并调度执行的。
那么这一章就是了解在这个流程中,不同的更新操作生成的任务其优先级是如何划分的,怎么去维护任务的顺序的。
任务优先级的分类
- React 事件优先级;
- lane 优先级;
- Schedule 优先级
事件优先级
事件优先级按照紧急程度分为以下几类
| EventPriority | 事件分类 | 事件来源 | Lane | 数值 |
|---|---|---|---|---|
| DiscreteEventPriority | 离散事件 | 例如 click、keydown、focusin 等,事件的触发不是连续,可以做到快速响应 | SyncLane | 0b0000000000000000000000000000001 |
| ContinuousEventPriority | 连续事件 | input、drag、scroll、mouseover 等,事件的是连续触发的,快速响应可能会阻塞渲染,优先级较离散事件低 | InputContinuousLane | 0b0000000000000000000000000000100 |
| DefaultEventPriority | 默认事件 | 默认事件 | DefaultLane | 0b0000000000000000000000000010000 |
| IdleEventPriority | 空闲的优先级 | 空闲的优先级 | IdleLane | 0b0100000000000000000000000000000 |
事件优先级的具体划分被定义在了 react-reconciler/src/ReactEventPriorities.new.js 文件中。
在每一个生成 update 的时候都先通过 requestUpdateLane(fiber)生成一个当前 update 的 lane 通道,下面我们看一下这个方法
js
export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases
// 获取当前的渲染模式
const mode = fiber.mode;
// 如果当前的模式不是 ConcurrentMode(并发模式),直接返回 SyncLane(同步通道)
if ((mode & ConcurrentMode) === NoMode) {
return (SyncLane: Lane);
} else if (
!deferRenderPhaseUpdateToNextBatch &&
// 是否在渲染阶段
(executionContext & RenderContext) !== NoContext &&
//
workInProgressRootRenderLanes !== NoLanes
) {
return pickArbitraryLane(workInProgressRootRenderLanes);
}
// TransitionLane 类型的事件
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
if (currentEventTransitionLane === NoLane) {
// All transitions within the same event are assigned the same lane.
currentEventTransitionLane = claimNextTransitionLane();
}
return currentEventTransitionLane;
}
// 如果当前更新处于 某些内部方法 或者 合成事件中,那就获取合成事件对应的 lane
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
}
// 如果当前更新处于 原生事件 中,那就获取根据事件的类型 去获取对应的 lane
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
}js
/**
* 根据传入的待处理的 lanes 中最高优先级的 lane, 返回其对应的事件优先级
* 在 Update的时候我们将事件转换成对应的 lane, 现在我们将lane转换成事件优先级
* 因为事件优先级取得是 lanes 中其通道范围最左边的一位(最大的1)
* @param {*} lanes
* @returns
*/
export function lanesToEventPriority(lanes: Lanes): EventPriority {
// 获取 lanes 中最高优先级的 lane 即最右边的 Lane
const lane = getHighestPriorityLane(lanes);
// 判断 Lane 是否小于1 ,小于则是离散事件
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
// 判断 Lane 是否小于4 ,小于则是连续事件
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
// 判断 Lane 是否小于8 ,小于则是默认事件
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}1. 合成事件的回调函数,如 onResize, 其会根据事件的名称找到对应的事件优先级
js
/**
* 根据事件的名称找到其对应的事件优先级
* @param {*} domEventName
* @returns DiscreteEventPriority等
*/
export function getEventPriority(domEventName: DOMEventName): * {
switch (domEventName) {
case "resize":
return DiscreteEventPriority;
}
}然后修改全局的事件优先级空间
js
function dispatchDiscreteEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent
) {
// 缓存之前的事件优先级
const previousPriority = getCurrentUpdatePriority();
// transition 缓存
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
try {
// 设置成 离散事件 事件空间
setCurrentUpdatePriority(DiscreteEventPriority);
// 绑定事件 事件函数
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
// 重置到之前的事件优先级
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}这样就可以将 onresize 回调函数中所有的更新都变成 DiscreteEventPriority 优先级的 update
2. Class 组件中渲染阶段的生命周期函数产生的更新
这些生命周期函数 是不建议使用 setState 去生成触发更新的,其更新都是内置的 ReplaceState 等类型的 update, 所以其 update 走的是 pickArbitraryLane(workInProgressRootRenderLanes) 的流程, 获取的是当前提交阶段优先级最高的 lane
3. useEffect、useLayoytEffect 等中的更新
js
/**
* 根据原生事件对象获取其对应的优先级,否则返回 DefaultEventPriority
* @returns
*/
export function getCurrentEventPriority(): * {
// 获取当前正在执行的原生事件对象
const currentEvent = window.event;
// 如果不存在 那就是 默认事件优先级
if (currentEvent === undefined) {
return DefaultEventPriority;
}
//
return getEventPriority(currentEvent.type);
}对于 Hooks 中的更新,其不在合成事件中,也不在原生事件中,所以其 update 的 lane 走的是 getCurrentEventPriority() 方法,获取的是 DefaultEventPriority
4. 原生事件产生的更新
对于原生事件产生的更新,其 update 的 lane 走的是 getEventPriority(domEventName) 方法,根据事件的名称获取其对应的事件优先级
js
export function getCurrentEventPriority(): * {
// 获取当前正在执行的原生事件对象
const currentEvent = window.event;
// 根据原生事件的名称 获取其对应的事件优先级
return getEventPriority(currentEvent.type);
}lane 优先级
更新的通道, 其定义使用了二进制变量,利用了位掩码的特性,在频繁运算的时候占用内存少,计算速度快。
每一个更新操作都会生成一个 lane, 有些事件是通过 事件 Event 进行触发的,有些事件是 React 内部或者 transition 等进行触发的, 这些事件的优先级都不一样。
且对于 transition 等,其存在很多状态的切换,所以对于每一个优先级其都按照 32 位二进制中的一段范围去划分。 具体的划分如下
| Lane | 数值 | 描述 |
|---|---|---|
| SyncLane | 0b0000000000000000000000000000001 | 同步通道 |
| InputContinuousLane | 0b0000000000000000000000000000100 | 输入连续通道 |
| DefaultLane | 0b0000000000000000000000000010000 | 默认通道 |
| IdleLane | 0b0100000000000000000000000000000 | 空闲通道 |
| NoLane | 0b0000000000000000000000000000000 | 没有车道 |
| NoLanes | 0b0000000000000000000000000000000 | 没有车道 |
Scheduler 优先级
对于并发模式下,在处理 update 任务的时候,其会根据不同的 lane 去生成对应的 Scheduler 优先级
js
// 获取lanes 中最高优先级的事件优先级
// 将 lane 转换成 事件优先级
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;
}通过获取 lanes 中最高优先级的 lane, 然后根据范围去将其聚合成 4 种事件优先级,然后再根据事件优先级去转换成 Scheduler 优先级, 然后再根据 Scheduler 优先级去生成对应的过期时间和任务队列。
| SchedulerPriority | 描述 | 数值 | 超时时间 | 事件优先级 |
|---|---|---|---|---|
| ImmediateSchedulerPriority | 立即执行的优先级 | 5 | 0ms | DiscreteEventPriority |
| UserBlockingSchedulerPriority | 用户阻塞的优先级 | 4 | 5ms | ContinuousEventPriority |
| NormalSchedulerPriority | 普通的优先级 | 3 | 10ms | DefaultEventPriority |
| LowSchedulerPriority | 低优先级 | 2 | 25ms | IdleEventPriority |
| IdleSchedulerPriority | 空闲的优先级 | 1 | 100ms | IdleEventPriority |
总结
对于一个更新我们可以大体按照以下流程去转换
触发的事件、函数等 决定了其(事件优先级)
根据事件优先级 和 mode 等生成其对应的 lane
对于并发模式,根据其优先级 转换成 Scheduler 优先级
Scheduler 优先级 生成 task 的过期时间 和 任务队列
