React Scheduler 中的优先级传递机制
React 的调度系统(Scheduler)是 React 并发模式的核心,它允许 React 根据任务的优先级来调度和执行工作。本文将深入探讨 React Scheduler 中的优先级传递机制,特别是 currentPriorityLevel 全局变量的作用以及优先级如何在不同的事件循环中传递。
currentPriorityLevel 的作用
在 React Scheduler 中,currentPriorityLevel 是一个全局变量,用于跟踪当前执行上下文的优先级:
var currentPriorityLevel = NormalPriority;这个全局变量有几个关键作用:
- 提供优先级上下文:让当前执行的代码知道它在什么优先级下运行
- 支持优先级嵌套:允许在不同优先级的上下文中嵌套执行代码
- 优先级传递:确保在特定优先级上下文中创建的更新继承该优先级
unstable_runWithPriority 的工作原理
unstable_runWithPriority 是 Scheduler 提供的一个核心 API,用于在指定优先级下执行代码:
function unstable_runWithPriority(priorityLevel, eventHandler) {
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
}这个函数的工作流程是:
- 保存当前的优先级
- 将当前优先级设置为指定的新优先级
- 执行传入的回调函数
- 无论回调是正常返回还是抛出异常,都恢复之前的优先级
这种模式确保了优先级上下文的正确嵌套,类似于作用域的管理。
为什么需要修改全局变量?
有人可能会问:为什么不直接将优先级作为参数传递给回调函数,而是要修改全局变量?这是因为:
- API 简洁性:避免了在整个调用链上传递优先级参数
- 隐式传递:允许深层嵌套的函数调用获取当前优先级,而不需要显式传递
- 与现有 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);
});在这个例子中:
setCount创建的更新对象会有UserBlockingPriority优先级setData创建的更新对象会有LowPriority优先级- 100ms 后,
wrappedCallback执行时会临时将优先级设置为UserBlockingPriority setName创建的更新对象也会有UserBlockingPriority优先级
优先级传递机制的重要性
这种优先级传递机制使 React 能够:
- 优先处理重要更新:用户交互触发的更新优先于后台任务
- 保持交互响应性:即使有大量低优先级工作,高优先级工作也能及时执行
- 优先级继承:由特定交互触发的所有更新都继承相同的优先级
- 优先级区分:不同来源的更新可以有不同的优先级
总结
React Scheduler 中的优先级传递机制是一个精心设计的系统,它通过:
- 使用全局
currentPriorityLevel变量跟踪当前优先级上下文 - 在更新对象上存储创建时的优先级信息
- 根据更新优先级调度相应优先级的任务
- 提供
unstable_wrapCallback确保异步回调在正确的优先级上下文中执行
这些机制共同确保了 React 能够智能地调度和处理不同优先级的工作,提供流畅的用户体验。