React Scheduler 原理
1. 为什么需要 Scheduler?
问题: 同步渲染的卡顿
React 15 的 Stack Reconciler 是同步递归的:
- 组件树很大时,JS 长时间占用主线程
- 掉帧、用户交互无响应
解决方案: 时间切片 (Time Slicing)
- 将渲染任务拆分为多个小块
- 每帧执行一部分,空闲时让出主线程
- 高优任务可打断低优任务
2. Lane 模型 (优先级)
React 18 使用 Lane (泳道) 表示更新优先级,用 31 位二进制位图表示。
优先级层级 (从高到低)
javascript
const SyncLane = 0b0000000000000000000000000000001; // 同步
const InputContinuousLane = 0b0000000000000000000000000000100; // 连续输入
const DefaultLane = 0b0000000000000000000000000010000; // 默认
const TransitionLane1 = 0b0000000000000000000001000000000; // Transition
const IdleLane = 0b0100000000000000000000000000000; // 空闲为什么用位图?
- 快速合并:
lanes | newLane - 快速检测:
lanes & SyncLane - 多更新可合并批处理
3. Scheduler 核心机制
任务队列
Scheduler 维护两个优先队列:
- timerQueue: 延迟任务 (startTime > currentTime)
- taskQueue: 可执行任务
调度循环
javascript
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
advanceTimers(currentTime); // 将到期的 timer 移到 taskQueue
currentTask = peek(taskQueue);
while (currentTask !== null && !shouldYield()) {
// shouldYield: 帧剩余时间 < 5ms 时让出
const callback = currentTask.callback;
if (typeof callback === 'function') {
const continuationCallback = callback(hasTimeRemaining);
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback; // 任务未完成
} else {
pop(taskQueue); // 任务完成
}
}
currentTask = peek(taskQueue);
}
return currentTask !== null; // 是否还有任务
}让出主线程
- 浏览器:
MessageChannel(优于setTimeout(0)) - 原因:
setTimeout有 4ms 最小延迟,MessageChannel无此限制
4. 与 React 的协作
Fiber 工作循环
javascript
function performConcurrentWorkOnRoot(root) {
const lanes = getNextLanes(root);
// 可中断的 Render 阶段
let exitStatus = renderRootConcurrent(root, lanes);
if (exitStatus === RootCompleted) {
// 不可中断的 Commit 阶段
commitRoot(root);
}
}中断与恢复
workLoopConcurrent每处理一个 Fiber 就检查shouldYield()- 中断时保存当前 Fiber (
workInProgress) - 恢复时从断点继续