Skip to content

任务优先级

在任务调度这一节,我们知道了怎么将一个更新操作转化成对应的 update, 然后根据 mode 去放到各自的任务栈中,并通过定时器、requestHostTimeout 去在一定的时间内将这些任务从对应的任务栈中取出,并调度执行的。

那么这一章就是了解在这个流程中,不同的更新操作生成的任务其优先级是如何划分的,怎么去维护任务的顺序的。

任务优先级的分类

  • React 事件优先级;
  • lane 优先级;
  • Schedule 优先级

事件优先级

事件优先级按照紧急程度分为以下几类

EventPriority事件分类事件来源Lane数值
DiscreteEventPriority离散事件例如 click、keydown、focusin 等,事件的触发不是连续,可以做到快速响应SyncLane0b0000000000000000000000000000001
ContinuousEventPriority连续事件input、drag、scroll、mouseover 等,事件的是连续触发的,快速响应可能会阻塞渲染,优先级较离散事件低InputContinuousLane0b0000000000000000000000000000100
DefaultEventPriority默认事件默认事件DefaultLane0b0000000000000000000000000010000
IdleEventPriority空闲的优先级空闲的优先级IdleLane0b0100000000000000000000000000000

事件优先级的具体划分被定义在了 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数值描述
SyncLane0b0000000000000000000000000000001同步通道
InputContinuousLane0b0000000000000000000000000000100输入连续通道
DefaultLane0b0000000000000000000000000010000默认通道
IdleLane0b0100000000000000000000000000000空闲通道
NoLane0b0000000000000000000000000000000没有车道
NoLanes0b0000000000000000000000000000000没有车道

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立即执行的优先级50msDiscreteEventPriority
UserBlockingSchedulerPriority用户阻塞的优先级45msContinuousEventPriority
NormalSchedulerPriority普通的优先级310msDefaultEventPriority
LowSchedulerPriority低优先级225msIdleEventPriority
IdleSchedulerPriority空闲的优先级1100msIdleEventPriority

总结

对于一个更新我们可以大体按照以下流程去转换

触发的事件、函数等 决定了其(事件优先级)

根据事件优先级 和 mode 等生成其对应的 lane

对于并发模式,根据其优先级 转换成 Scheduler 优先级

Scheduler 优先级 生成 task 的过期时间 和 任务队列