Skip to content

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)
  • 恢复时从断点继续

5. 手写简易 Scheduler

code/scheduler-demo.js

前端面试知识库