【React源码11】深入学习React 源码实现——调度器(Scheduler)底层实现

深入学习React源码实现之调度器(Scheduler)底层实现


一、调度器(Scheduler)历史介绍

在 React 的早期版本中,所有的更新都是同步执行的。这意味着一旦触发 setState 或组件渲染,整个渲染过程会立即完成,期间无法中断,导致页面在复杂应用中出现卡顿。

为了解决这个问题,React 团队从 React 16 开始引入了 Fiber 架构并发模式(Concurrent Mode),其中的关键组成部分就是 React 调度器(Scheduler)

Scheduler 的作用:

  • 实现任务优先级调度
  • 支持时间片调度(Time Slicing)
  • 允许中断和恢复长时间任务
  • 提供基于浏览器 API(如 MessageChannel、setTimeout)的异步执行机制

相关源码文件:

文件路径功能
packages/scheduler/src/Scheduler.js核心调度逻辑
packages/scheduler/src/SchedulerPriorities.js定义 LanePriority 类型
packages/scheduler/src/forks/SchedulerHostConfig.default.js平台适配层(DOM 实现)
react-reconciler/src/ReactFiberWorkLoop.old.js使用 Scheduler 的协调器入口

二、算法设计思路与核心概念

1. 设计目标

  • 支持不同优先级任务调度
  • 允许高优先级任务打断低优先级任务
  • 使用时间片调度避免主线程阻塞
  • 兼容多种平台(Web、Native)

2. 核心数据结构

(1) Task 对象结构
{
  id: number,              // 唯一标识符
  callback: Function,      // 回调函数
  priorityLevel: number,   // 优先级等级
  startTime: number,       // 可开始执行的时间戳
  expirationTime: number,  // 截止时间
  sortIndex: number,       // 排序依据
}
(2) 优先级枚举(LanePriority)
export const NoPriority = 0;
export const ImmediatePriority = 1;   // 立即执行
export const UserBlockingPriority = 2; // 用户感知阻塞
export const NormalPriority = 3;     // 正常优先级
export const LowPriority = 4;        // 低优先级
export const IdlePriority = 5;       // 空闲时才执行

📄 源码位置:packages/scheduler/src/SchedulerPriorities.js


三、调度流程图解

requestIdleCallback / setTimeout
      ↓
创建 Task 并插入 taskQueue
      ↓
根据优先级排序(sortIndex)
      ↓
进入 workLoop 阶段
      ↓
判断是否超时或可继续执行
      ↓
执行回调函数
      ↓
返回 next 单位任务或 null(表示结束)

四、手动实现一个简化版 Scheduler

下面是一个简化版的 React Scheduler 实现,包含优先级调度、时间片控制、任务中断恢复等核心功能。

✅ 特性支持:

  • 时间片调度(每帧最多执行 5ms)
  • 优先级调度(Immediate > Blocking > Normal > Low > Idle)
  • 支持中断并恢复任务

✨ 源码实现如下:

// 定义优先级常量
const NoPriority = 0; // 无优先级(通常用于占位)
const ImmediatePriority = 1; // 最高优先级(立即执行,如用户交互)
const UserBlockingPriority = 2; // 用户阻塞任务(如动画)
const NormalPriority = 3; // 默认优先级(如普通渲染)
const LowPriority = 4; // 低优先级(如非关键更新)
const IdlePriority = 5; // 空闲时执行(如日志上报)

// 任务队列(按优先级和截止时间排序)
let taskQueue = []; // 存储待执行的任务
let currentTask = null; // 当前正在执行的任务
let isPerformingWork = false; // 标记是否正在执行任务(防止重入)

// 获取当前时间戳(高精度时间)
function getCurrentTime() {
  return performance.now(); // 返回当前时间戳(单位:毫秒)
}

// 插入任务到队列中(按优先级和截止时间排序)
function insertTask(task) {
  // 计算任务的排序索引:优先按截止时间,其次按优先级
  task.sortIndex = task.expirationTime !== 0 ? task.expirationTime : task.startTime + 5000; // 默认延迟5秒

  // 遍历队列,找到合适的插入位置
  let i = 0;
  for (; i < taskQueue.length; i++) {
    const existing = taskQueue[i];
    if (
      existing.priorityLevel > task.priorityLevel || // 优先级更高的任务在前
      (existing.priorityLevel === task.priorityLevel && existing.sortIndex > task.sortIndex) // 优先级相同时,截止时间更早的任务在前
    ) {
      break;
    }
  }
  taskQueue.splice(i, 0, task); // 插入任务到正确位置
}

// 计算截止时间(当前时间 + 5ms)
let deadline = 0; // 截止时间(由 requestIdleCallback 动态设置)
function shouldYieldToHost() {
  return getCurrentTime() >= deadline; // 检查是否超过截止时间(需要让出主线程)
}

// 执行单个任务
function performUnitOfWork(task) {
  const result = task.callback(); // 执行任务回调
  if (typeof result === 'function') {
    task.callback = result; // 如果返回函数,则作为新的回调(支持任务拆分)
  } else {
    task.callback = null; // 否则清空回调(任务完成)
  }
}

// 主工作循环
function workLoop(deadlineArg) {
  // 设置截止时间:如果超时,则截止时间为最大值;否则为剩余时间 + 当前时间
  deadline = deadlineArg.didTimeout ? Number.MAX_SAFE_INTEGER : deadlineArg.timeRemaining() + getCurrentTime();

  // 循环执行任务,直到队列为空或需要让出主线程
  while (taskQueue.length > 0 && !shouldYieldToHost()) {
    currentTask = taskQueue[0]; // 获取队列中的第一个任务

    // 检查任务是否过期(强制执行)
    if (currentTask.expirationTime !== 0 && currentTask.expirationTime <= getCurrentTime()) {
      performUnitOfWork(currentTask); // 执行任务
      taskQueue.shift(); // 移除已完成的队列
    } else {
      // 执行任务回调
      const result = currentTask.callback();
      if (typeof result === 'function') {
        currentTask.callback = result; // 更新回调(支持任务拆分)
      } else {
        taskQueue.shift(); // 移除已完成的队列
      }
    }
  }

  // 如果队列中仍有任务,重新调度
  if (taskQueue.length > 0) {
    schedulePerformWorkUntilIdle();
  }
}

// 启动调度器(利用 requestIdleCallback 在空闲时执行)
function schedulePerformWorkUntilIdle() {
  requestIdleCallback(workLoop, { timeout: 500 }); // 设置超时时间为 500ms
}

// 调度接口(对外暴露的 API)
function unstable_scheduleCallback(priorityLevel, callback, options = {}) {
  const startTime = getCurrentTime(); // 获取当前时间戳
  const timeout = options.timeout || (() => {
    // 根据优先级设置默认超时时间
    switch (priorityLevel) {
      case ImmediatePriority:
        return -1; // 立即执行(无超时)
      case UserBlockingPriority:
        return 250; // 250ms(动画帧级别)
      case NormalPriority:
        return 10000; // 10s(默认任务)
      case LowPriority:
        return 10000; // 10s(低优先级任务)
      case IdlePriority:
        return 100000000; // 100s(空闲任务)
    }
  })();

  const expirationTime = startTime + timeout; // 计算截止时间

  // 创建新任务对象
  const newTask = {
    callback, // 任务回调函数
    priorityLevel, // 任务优先级
    startTime, // 任务开始时间
    expirationTime, // 任务截止时间
    sortIndex: 0, // 排序索引(由 insertTask 计算)
  };

  insertTask(newTask); // 插入任务到队列
  schedulePerformWorkUntilIdle(); // 启动调度器
}

// 导出接口(用于调试或扩展)
window.unstable_scheduleCallback = unstable_scheduleCallback;
window.ImmediatePriority = ImmediatePriority;
window.UserBlockingPriority = UserBlockingPriority;
window.NormalPriority = NormalPriority;
window.LowPriority = LowPriority;
window.IdlePriority = IdlePriority;

五、完整代码注释说明

  • insertTask():将新任务插入有序队列,按优先级和截止时间排序。
  • workLoop():主工作循环,判断是否可以继续执行任务。
  • shouldYieldToHost():判断当前是否应该让出主线程。
  • schedulePerformWorkUntilIdle():使用 requestIdleCallback 启动调度。
  • unstable_scheduleCallback():对外暴露的调度接口,用于注册任务。

六、设计模式分析

设计模式应用场景
策略模式不同优先级对应不同的调度策略(如 Immediate、Normal)
观察者模式任务完成后通知调度器继续执行
责任链模式多个任务依次执行,形成调度链
状态机模式任务具有不同的状态(等待、执行、完成)
命令模式将每个任务封装为可执行的回调对象
工厂模式创建任务对象并插入队列

七、10大调度器(Scheduler)高频面试题

  1. React 的 Scheduler 是做什么的?为什么需要它?

    • 用于实现并发调度,支持中断和恢复任务,提升响应速度。
  2. Scheduler 如何支持不同优先级的任务?

    • 使用 LanePriority 表示优先级,并在调度时按优先级排序执行。
  3. 什么是时间片调度(Time Slicing)?它是如何实现的?

    • 每帧只执行一部分任务,剩余任务延迟执行,通过 requestIdleCallback 实现。
  4. 如何判断一个任务是否需要中断?

    • 通过 deadline 判断是否已超时,若超时则中断当前任务。
  5. Scheduler 是如何利用浏览器的 requestIdleCallback 的?

    • 在空闲时间执行任务,避免阻塞主线程。
  6. 如何理解 “callback 返回函数” 的续执行机制?

    • 如果 callback 返回函数,表示该任务未完成,下次继续执行。
  7. 什么是任务的 expirationTime?它的作用是什么?

    • 表示任务的截止时间,超过这个时间后即使低优先级也要执行。
  8. Scheduler 是如何支持并发更新的?

    • 通过多个任务并行执行、优先级排序、中断恢复机制实现。
  9. useTransition 和 useDeferredValue 是如何影响 Scheduler 的?

    • 降低某些更新的优先级,使其不打断用户交互。
  10. Scheduler 是如何跨平台兼容的?

    • 抽象 HostConfig 层,根据不同平台(Web、Native)实现底层调度逻辑。

八、总结

React 的 Scheduler 是其并发调度系统的核心模块,它不仅解决了传统同步更新带来的性能瓶颈,还为构建高性能、响应性强的现代 Web 应用提供了坚实基础。

通过本文的学习,你已经掌握了以下内容:

  • Scheduler 的设计思想与历史演进
  • LanePriority 的定义与映射机制
  • Scheduler 的核心算法与流程
  • 一个完整的简化版 Scheduler 实现
  • 设计模式的应用分析
  • 10道高频面试题解析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈前端老曹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值