1529 words
8 minutes
React Scheduler 中的优先级传递机制
2025-03-13 22:49:35
2025-12-24 23:45:46

← 返回 Scheduler 主页

React Scheduler 中的优先级传递机制#

React 的调度系统(Scheduler)是 React 并发模式的核心,它允许 React 根据任务的优先级来调度和执行工作。本文将深入探讨 React Scheduler 中的优先级传递机制,特别是 currentPriorityLevel 全局变量的作用以及优先级如何在不同的事件循环中传递。

currentPriorityLevel 的作用#

在 React Scheduler 中,currentPriorityLevel 是一个全局变量,用于跟踪当前执行上下文的优先级:

var currentPriorityLevel = NormalPriority;

这个全局变量有几个关键作用:

  1. 提供优先级上下文:让当前执行的代码知道它在什么优先级下运行
  2. 支持优先级嵌套:允许在不同优先级的上下文中嵌套执行代码
  3. 优先级传递:确保在特定优先级上下文中创建的更新继承该优先级

unstable_runWithPriority 的工作原理#

unstable_runWithPriority 是 Scheduler 提供的一个核心 API,用于在指定优先级下执行代码:

function unstable_runWithPriority(priorityLevel, eventHandler) {
  var previousPriorityLevel = currentPriorityLevel;
  currentPriorityLevel = priorityLevel;

  try {
    return eventHandler();
  } finally {
    currentPriorityLevel = previousPriorityLevel;
  }
}

这个函数的工作流程是:

  1. 保存当前的优先级
  2. 将当前优先级设置为指定的新优先级
  3. 执行传入的回调函数
  4. 无论回调是正常返回还是抛出异常,都恢复之前的优先级

这种模式确保了优先级上下文的正确嵌套,类似于作用域的管理。

为什么需要修改全局变量?#

有人可能会问:为什么不直接将优先级作为参数传递给回调函数,而是要修改全局变量?这是因为:

  1. API 简洁性:避免了在整个调用链上传递优先级参数
  2. 隐式传递:允许深层嵌套的函数调用获取当前优先级,而不需要显式传递
  3. 与现有 API 集成:许多 React API(如 setState)不接受优先级参数,但内部需要知道当前优先级

优先级在事件循环中的传递#

React 中的状态更新(如 setState)通常会被调度到未来的事件循环中执行。那么,优先级是如何跨事件循环传递的呢?这个过程分为几个关键步骤:

1. 事件处理阶段#

当事件处理函数在 unstable_runWithPriority 中执行时:

unstable_runWithPriority(UserBlockingPriority, () => {
  // 此时 currentPriorityLevel = UserBlockingPriority
  setCount(count + 1);
});

在这个阶段,currentPriorityLevel 被设置为 UserBlockingPriority

2. 更新创建阶段#

setCount 被调用时,React 内部会:

// 简化的 setState 内部实现
function enqueueStateUpdate(fiber, queue, update) {
  // 捕获当前优先级
  const currentPriority = unstable_getCurrentPriorityLevel();
  
  // 将优先级转换为 lane
  const lane = schedulerPriorityToLane(currentPriority);
  
  // 将优先级信息附加到更新对象上
  update.lane = lane;
  
  // 将更新加入队列
  queue.pending = update;
  
  // 调度渲染
  scheduleUpdateOnFiber(fiber, lane);
}

关键点:更新对象本身存储了创建时的优先级信息。这是优先级跨事件循环传递的核心机制。

3. 调度更新阶段#

scheduleUpdateOnFiber 最终会调用 scheduleCallback

function scheduleUpdateOnFiber(fiber, lane) {
  // ...
  
  // 将 lane 转换回 scheduler 优先级
  const schedulerPriority = lanesToSchedulerPriority(lane);
  
  // 使用这个优先级调度任务
  scheduleCallback(
    schedulerPriority,
    performConcurrentWorkOnRoot.bind(null, root)
  );
}

这里,更新对象的优先级被用来决定调度任务的优先级。

4. 任务执行阶段#

在下一个事件循环中,当调度系统执行这个任务时:

// 在 workLoop 中
const callback = currentTask.callback;
if (typeof callback === 'function') {
  // 设置当前优先级为任务的优先级
  currentPriorityLevel = currentTask.priorityLevel;
  
  // 执行回调
  const continuationCallback = callback(didTimeout);
  // ...
}

此时,currentPriorityLevel 会被设置为任务的优先级,任务开始处理之前捕获的更新。

unstable_wrapCallback 的作用#

unstable_wrapCallback 是另一个与优先级传递相关的重要 API:

function unstable_wrapCallback(callback) {
  var parentPriorityLevel = currentPriorityLevel;
  
  return function() {
    var previousPriorityLevel = currentPriorityLevel;
    currentPriorityLevel = parentPriorityLevel;
    
    try {
      return callback.apply(this, arguments);
    } finally {
      currentPriorityLevel = previousPriorityLevel;
    }
  };
}

这个函数创建一个包装回调,该回调在未来执行时会恢复创建它时的优先级上下文。这对于确保异步回调(如 setTimeout 或 Promise 回调)在正确的优先级上下文中执行非常重要。

具体示例#

让我们通过一个具体示例来理解这个机制:

// 初始优先级是 NormalPriority
console.log("初始优先级:", unstable_getCurrentPriorityLevel());

// 用户点击事件
unstable_runWithPriority(UserBlockingPriority, () => {
  console.log("点击处理优先级:", unstable_getCurrentPriorityLevel()); // UserBlockingPriority
  
  // 创建一个高优先级更新
  setCount(c => c + 1);
  
  // 创建一个包装的回调,它会在未来执行时恢复 UserBlockingPriority
  const wrappedCallback = unstable_wrapCallback(() => {
    console.log("包装回调优先级:", unstable_getCurrentPriorityLevel()); // UserBlockingPriority
    setName("Alice");
  });
  
  // 稍后执行这个回调
  setTimeout(wrappedCallback, 100);
});

console.log("事件处理后优先级:", unstable_getCurrentPriorityLevel()); // NormalPriority

// 低优先级操作
unstable_runWithPriority(LowPriority, () => {
  console.log("低优先级操作:", unstable_getCurrentPriorityLevel()); // LowPriority
  setData(newData);
});

在这个例子中:

  1. setCount 创建的更新对象会有 UserBlockingPriority 优先级
  2. setData 创建的更新对象会有 LowPriority 优先级
  3. 100ms 后,wrappedCallback 执行时会临时将优先级设置为 UserBlockingPriority
  4. setName 创建的更新对象也会有 UserBlockingPriority 优先级

优先级传递机制的重要性#

这种优先级传递机制使 React 能够:

  1. 优先处理重要更新:用户交互触发的更新优先于后台任务
  2. 保持交互响应性:即使有大量低优先级工作,高优先级工作也能及时执行
  3. 优先级继承:由特定交互触发的所有更新都继承相同的优先级
  4. 优先级区分:不同来源的更新可以有不同的优先级

总结#

React Scheduler 中的优先级传递机制是一个精心设计的系统,它通过:

  1. 使用全局 currentPriorityLevel 变量跟踪当前优先级上下文
  2. 在更新对象上存储创建时的优先级信息
  3. 根据更新优先级调度相应优先级的任务
  4. 提供 unstable_wrapCallback 确保异步回调在正确的优先级上下文中执行

这些机制共同确保了 React 能够智能地调度和处理不同优先级的工作,提供流畅的用户体验。

React Scheduler 中的优先级传递机制
https://0bipinnata0.my/posts/react/handwritten/scheduler/priority-propagation/
Author
0bipinnata0
Published at
2025-03-13 22:49:35