‌React调度系统Scheduler深度解析‌

# React 调度系统 Scheduler 深度解析

在 React 中,**调度系统(Scheduler)** 是负责管理任务优先级、拆分工作并在合适时机执行的底层模块。它让 React 能够在保持界面流畅的同时,以合理的优先级顺序执行各种更新任务。本文将从 Scheduler 的核心概念、源码结构、任务优先级、工作循环(Work Loop)及常用 API 等方面进行深度解析,结合代码示例与 ASCII 图解,帮你理清它的实现逻辑与使用方式。

---

## 目录

1. [前言:为何需要调度系统](#前言为何需要调度系统)  
2. [Scheduler 核心概念](#scheduler-核心概念)  
   1. [任务优先级(Priority Levels)](#任务优先级priority-levels)  
   2. [时间切片与让出(Time Slicing & Yielding)](#时间切片与让出time-slicing--yielding)  
   3. [Callback 与 Task](#callback-与-task)  
3. [Scheduler API 及典型代码示例](#scheduler-api-及典型代码示例)  
   1. [安装与导入](#安装与导入)  
   2. [调度一个低优先级任务](#调度一个低优先级任务)  
   3. [判断是否应该让出(`shouldYieldToHost`)](#判断是否应该让出shouldyieldtohost)  
4. [Scheduler 源码结构与关键模块](#scheduler-源码结构与关键模块)  
   1. [`Scheduler.js` 主入口](#schedulersjs-主入口)  
   2. [`SchedulerHostConfig`](#schedulerhostconfig)  
   3. [优先级枚举与内部实现](#优先级枚举与内部实现)  
   4. [任务队列与环形链表](#任务队列与环形链表)  
5. [工作循环(Work Loop)深度剖析](#工作循环work-loop深度剖析)  
   1. [同步模式 Work Loop](#同步模式-work-loop)  
   2. [并发模式 Work Loop](#并发模式-work-loop)  
   3. [`performWorkUntilDeadline` 如何中断与恢复](#performworkuntildeadline-如何中断与恢复)  
6. [任务优先级调度流程图解](#任务优先级调度流程图解)  
7. [基于 Scheduler 实现简易任务调度示例](#基于-scheduler-实现简易任务调度示例)  
8. [常见误区与优化建议](#常见误区与优化建议)  
9. [总结与学习建议](#总结与学习建议)  

---

## 前言:为何需要调度系统

在传统的单线程 JavaScript 环境中,UI 渲染和业务逻辑都在同一个线程上执行。如果有一个耗时操作(如大规模数据处理、复杂的布局计算等)直接在主线程执行,就会导致界面卡顿、动画丢帧,造成用户体验下降。为了解决这一问题,React 引入了 **调度系统(Scheduler)**,负责将大任务拆分为若干小“工作单元(work unit)”,并结合浏览器空闲时间片(`requestIdleCallback` 或轮询 `postMessage`)动态切换执行。

Scheduler 的核心价值在于:

- **控制更新优先级**:不同类型的操作(用户交互、动画、数据更新)具有不同紧急程度,Scheduler 允许我们为任务标记优先级,从而先执行高优先级任务,后执行低优先级任务。
- **分片渲染(Time Slicing)**:将一个大任务拆成多个小任务,保证每段执行时间不会超过阈值,让出主线程给浏览器进行渲染与用户交互。
- **可中断与恢复**:在执行过程中,若遇到更高优先级任务到来,可暂停当前任务,待高优先级任务完成后再恢复执行,提高响应速度。

接下来我们先从核心概念讲起。

---

## Scheduler 核心概念

### 任务优先级(Priority Levels)

Scheduler 把任务优先级分为五档,源码中用常量说明:

```js
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;
  1. ImmediatePriority(同步任务)

    • 优先级最高,用于需要立即执行的任务,例如 React 的同步更新(事件处理函数中的 setState)。
    • 这类任务会同步完成,不会中断。
  2. UserBlockingPriority(用户阻塞任务)

    • 比如用户点击、输入等离散事件,需要尽快响应。
    • 在并发模式下,这类任务会被优先安排。
  3. NormalPriority(普通任务)

    • 普通更新(如异步数据更新)通常属于此类。
    • 可以被更高优先级任务打断。
  4. LowPriority(低优先级任务)

    • 不急要的后台更新,例如预取数据、日志上报等。
    • 在主线程空闲时才会执行。
  5. IdlePriority(空闲任务)

    • 最低优先级,只有在页面长时间空闲(没有更高优先级任务)时才会执行。
    • 适合做缓存清理、统计埋点等“可延后”工作。

时间切片与让出(Time Slicing & Yielding)

浏览器每一帧大约有 16ms 的时间可用,当任务执行超过阈值(通常 5ms 左右)后,应让出主线程,让浏览器完成渲染、处理用户输入,再在下一帧继续执行剩余任务。Scheduler 借助以下原语实现这一逻辑:

  • requestIdleCallback / cancelIdleCallback

    • 在主线程空闲时执行回调,参数中包含 timeRemaining() 来判断剩余时间。
    • 若不支持 requestIdleCallback(如部分浏览器),Scheduler 会使用 postMessagesetTimeout 模拟实现。
  • shouldYieldToHost() / unstable_shouldYield()

    • 调用以检查当前帧是否剩余足够时间,若不足则应中断当前任务并安排下次继续。
  • 时间阈值(deadline)

    • 默认为 \~5ms,每次运行 Work Loop 时会计算开始时间与当前时间差,若超过阈值则暂停。

Callback 与 Task

Scheduler 内部维护一条优先级任务队列,每个任务用一个 CallbackNode 表示。一个任务(callback)至少包含以下属性:

type CallbackNode = {
  callback: (expirationTime: number) => any,
  priorityLevel: number,
  expirationTime: number,
  next: CallbackNode | null,
  previous: CallbackNode | null,
};
  • callback:实际要执行的函数,一旦调度到 CPU 空闲就会调用它。
  • priorityLevel:任务的优先级,决定它在队列中的排序。
  • expirationTime:任务的过期时间,若到期仍未执行,则应立即调度执行。
  • next / previous:形成一个环形双向链表,用以管理任务队列。

当我们调用 unstable_scheduleCallback(priorityLevel, callback, options) 时:

  1. 创建一个新的 CallbackNode,设置好 priorityLevelexpirationTime(默认过期时间依赖优先级)。
  2. 将该节点插入到已有任务队列的合适位置,确保链表按优先级与过期时间排序。
  3. 如果队列中不存在正在执行的工作循环(work loop),则调用 requestIdleCallback 提交对 performWorkUntilDeadline 的调度。

Scheduler API 及典型代码示例

安装与导入

如果你使用的是 React 17+,Scheduler 已包含在 React 包中;也可单独安装使用最新版本:

# React 内置(React 17 以后)
import {
  unstable_scheduleCallback as scheduleCallback,
  unstable_UserBlockingPriority as UserBlockingPriority,
  unstable_NormalPriority as NormalPriority,
  unstable_shouldYield as shouldYield,
} from 'scheduler';

# 或单独安装
npm install scheduler
# 然后导入同上

调度一个低优先级任务

下面示例演示如何调度一个低优先级任务,并在可用时逐步执行计算密集型操作,而不中断用户交互体验。

import React, { useState, useRef } from 'react';
import { Button, View } from 'react-native';
import {
  unstable_scheduleCallback as scheduleCallback,
  unstable_LowPriority as LowPriority,
  unstable_shouldYield as shouldYield,
} from 'scheduler';

export default function HeavyComputationDemo() {
  const [result, setResult] = useState(null);
  const workLoopId = useRef(null);

  // 一个模拟“重度计算”的大循环
  const heavyComputation = () => {
    let i = 0;
    const max = 1e8;
    let sum = 0;

    function work() {
      // 分片执行:每次计算 10000 次后检查是否应让出
      const chunkSize = 10000;
      for (let c = 0; c < chunkSize && i < max; c++, i++) {
        sum += Math.sqrt(i);
      }

      if (i < max && !shouldYield()) {
        // 还没到最大,并且当前帧还有空闲时间,继续执行
        workLoopId.current = scheduleCallback(LowPriority, work);
      } else if (i < max) {
        // 当前帧时间耗尽,下一帧继续
        workLoopId.current = scheduleCallback(LowPriority, work);
      } else {
        // 计算完成,更新结果
        setResult(sum);
      }
    }

    work();
  };

  return (
    <View style={{ padding: 20 }}>
      <Button title="开始重度计算" onPress={heavyComputation} />
      {result !== null && <Text>计算结果:{result}</Text>}
    </View>
  );
}

示例说明:

  1. 点击 “开始重度计算” 后,入口函数 heavyComputation 启动一个分片工作 work
  2. 每次循环固定执行 chunkSize 次(如 10,000 次),然后用 shouldYield() 判断当前帧是否剩余时间。
  3. 若时间未耗尽并且尚未完成所有循环,就通过 scheduleCallback(LowPriority, work) 安排下一批任务。
  4. 当循环完成后,将最终 sum 存入组件状态。此时即使有其他高优先级任务(如点击按钮、滚动),Scheduler 也会中断当前批次、先让高优先级任务执行。

判断是否应该让出(shouldYieldToHost

shouldYield(alias unstable_shouldYield)是 Scheduler 暴露给用户检查当前帧是否应当让出的 API,底层会调用 SchedulerHostConfig.shouldYieldToHost()。在浏览器环境下,它会基于 requestIdleCallbacktimeRemaining();在 React Native 环境下(或不支持 requestIdleCallback),会使用 postMessage 垒式触发“微任务”并监测时间差。

简化版伪代码:

let frameDeadline = 0;
let isMessageLoopRunning = false;

// 当浏览器空闲时(requestIdleCallback)或 setTimeout 触发时:
function performWorkUntilDeadline(deadline) {
  frameDeadline = deadline.timeRemaining() + getCurrentTime();
  isMessageLoopRunning = true;
  workLoopConcurrent();
  isMessageLoopRunning = false;
  // 如果未完成所有任务,再次调度 performWorkUntilDeadline
}

export function unstable_shouldYield() {
  // 当前时间超过 deadline,就应该让出
  return getCurrentTime() >= frameDeadline;
}

workLoopConcurrent 则会在每个单元执行后调用 unstable_shouldYield(),若返回 true,则中断循环并安排下一空闲时段继续。


Scheduler 源码结构与关键模块

下面让我们走近 Scheduler 的源码,剖析其目录结构与模块职责。

Scheduler.js 主入口

node_modules/scheduler/index.js(或 React 内置 react/src/ReactSharedInternals/scheduler)中,主要暴露的 API 包括:

export {
  unstable_scheduleCallback as scheduleCallback,
  unstable_cancelCallback as cancelCallback,
  unstable_shouldYield as shouldYield,
  unstable_runWithPriority as runWithPriority,
  unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
  unstable_requestPaint as requestPaint,
  unstable_now as now,
  unstable_ImmediatePriority as ImmediatePriority,
  unstable_UserBlockingPriority as UserBlockingPriority,
  unstable_NormalPriority as NormalPriority,
  unstable_LowPriority as LowPriority,
  unstable_IdlePriority as IdlePriority,
} from './scheduler';

这些函数和常量封装在 scheduler/src/forks/Scheduler.js(或同名文件)中。核心逻辑主要分为以下几类文件:

  • Scheduler.js:高层 API 定义,调用底层实现。
  • SchedulerHostConfig.*:不同环境(浏览器、React Native、Node.js)的适配配置。
  • SchedulerImplementation.*:核心算法实现,包括任务队列、调度逻辑、Work Loop 等。

SchedulerHostConfig

Scheduler 需要与宿主环境(Host)交互,比如获取当前时间、注册空闲回调、取消空闲回调、判断是否让出等。SchedulerHostConfig 定义了这些接口的默认实现,有多套实现:

  • 浏览器环境:用 requestIdleCallbackpostMessageperformance.now()
  • React Native 环境:没有 requestIdleCallback,使用 setTimeout(..., 0) 或直接基于 global.performance.now() 实现。
  • Node.js 环境:用 setImmediateprocess.hrtime 实现高精度计时与空闲回调。

以浏览器为例,简化版实现:

// SchedulerHostConfig.browser.js
export const requestHostCallback = (cb) => {
  requestIdleCallback(cb, { timeout: 1 });
};

export const cancelHostCallback = (cbID) => {
  cancelIdleCallback(cbID);
};

export const shouldYieldToHost = () => {
  // 当前时间超过预设 deadline
  return getCurrentTime() >= frameDeadline;
};

export const now = () => {
  return performance.now();
};

在 React Native 中,这些函数会映射到 setTimeoutclearTimeoutglobal.performance.now() 等实现。

优先级枚举与内部实现

Scheduler 通过一个名为 PriorityLevel 的枚举管理优先级,并根据优先级划分“过期时间”。核心常量如下:

export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

// 对应的过期延迟(毫秒)
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Infinity

当调用 scheduleCallback(priorityLevel, callback) 时,会计算该任务的 过期时间

const currentTime = now();
let timeout;
switch (priorityLevel) {
  case ImmediatePriority:
    timeout = IMMEDIATE_PRIORITY_TIMEOUT;
    break;
  case UserBlockingPriority:
    timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
    break;
  case NormalPriority:
    timeout = NORMAL_PRIORITY_TIMEOUT;
    break;
  case LowPriority:
    timeout = LOW_PRIORITY_TIMEOUT;
    break;
  case IdlePriority:
    timeout = IDLE_PRIORITY_TIMEOUT;
    break;
}
const expirationTime = currentTime + timeout;

然后将任务按 (expirationTime, priorityLevel) 排序后插入环形链表,这样可以保证:

  • 过期时间更早的任务优先调度。
  • 同过期时间时,优先级更高的任务先执行。

任务队列与环形链表

Scheduler 用一个双向环形链表维护所有待执行任务。伪代码如下:

let firstTask = null; // 指向链表中的第一个节点
let lastTask = null;

// 新增任务
function scheduleCallback(priorityLevel, callback) {
  const currentTime = now();
  const expirationTime = currentTime + getTimeoutForPriority(priorityLevel);
  const newTask = {
    callback,
    priorityLevel,
    expirationTime,
    next: null,
    previous: null,
  };

  if (firstTask === null) {
    firstTask = lastTask = newTask;
    newTask.next = newTask.previous = newTask;
  } else {
    // 插入到链表尾部(简单示例,不按过期时间排序)
    lastTask.next = newTask;
    newTask.previous = lastTask;
    newTask.next = firstTask;
    firstTask.previous = newTask;
    lastTask = newTask;
  }

  // 如果当前没有调度回调,就安排 performWorkUntilDeadline
  if (!isSchedulerCallbackScheduled) {
    isSchedulerCallbackScheduled = true;
    requestHostCallback(performWorkUntilDeadline);
  }

  return newTask; // 可用于取消
}

在生产环境中,Scheduler 会在插入时按过期时间与优先级排序,以保证最紧急的任务先执行。


工作循环(Work Loop)深度剖析

调度器主要有两种运行模式:同步模式(Sync Work Loop)并发模式(Concurrent Work Loop)。它们都基于“拆分任务为小块、轮询执行并在合适时机让出”这一思路,但并发模式更注重中断与恢复。

同步模式 Work Loop

当调度同步任务(ImmediatePriority)时,Scheduler 不会分片中断,而是一次性执行完所有队列中同一优先级的任务。简化版伪码:

function workLoopSync() {
  while (firstTask !== null) {
    const currentTask = firstTask;
    // 先移除该任务
    removeTaskFromList(currentTask);

    // 执行任务
    currentTask.callback(currentTask.expirationTime);
    // 如果 callback 返回了一个新的 callback(未完成),则重新调度
    if (typeof currentTask.callback === 'function') {
      scheduleCallback(currentTask.priorityLevel, currentTask.callback);
    }
  }
  isSchedulerCallbackScheduled = false;
}
  • 由于同步任务优先级最高,不会调用 shouldYield,只要队列中还有任务就一直执行。
  • 如果在任务执行过程中调用了 scheduleCallback(UserBlockingPriority, someTask),也会插入队列,待当前同步循环结束后再执行。

并发模式 Work Loop

并发模式下,Scheduler 会定期调用 unstable_shouldYield() 判断当前帧时间是否耗尽,从而中断循环并安排下一空闲周期继续。主要伪码如下:

let currentDeadline = 0;

function performWorkUntilDeadline(deadline) {
  currentDeadline = deadline.timeRemaining() + now();
  isSchedulerCallbackScheduled = false;
  workLoopConcurrent();
}

function workLoopConcurrent() {
  while (firstTask !== null) {
    // 1. 如果任务已过期,则同步立即执行
    const currentTime = now();
    const currentTask = firstTask;
    if (currentTask.expirationTime <= currentTime) {
      // 过期任务同步执行
      removeTaskFromList(currentTask);
      currentTask.callback(currentTask.expirationTime);
      continue;
    }
    // 2. 非过期任务,根据优先级执行一个单元
    if (shouldYield()) {
      // 时间片用尽,安排下一轮继续
      scheduleHostCallback();
      return;
    } else {
      // 尚有时间片,执行任务
      removeTaskFromList(currentTask);
      const continuationCallback = currentTask.callback(currentTask.expirationTime);
      if (typeof continuationCallback === 'function') {
        // 如果任务没有完成,返回一个 continuation callback,重新插入队列
        scheduleCallback(currentTask.priorityLevel, continuationCallback);
      }
    }
  }
}

关键点:

  1. 过期任务立即执行:若 expirationTime <= now(),无论当前帧是否剩余时间,都同步执行。
  2. 判断是否让出:在执行非过期任务前,调用 shouldYield()。若返回 true,表明当前帧剩余时间不足,需暂时让出主线程,安排 performWorkUntilDeadline 在下一空闲时段再次执行。
  3. 任务拆分与继续:如果某个任务内部意识到自己尚未完成(例如 React 的 Fiber 单元),会返回一个“后续回调”(continuation callback),由调度器重新插入队列,下一轮继续执行。

performWorkUntilDeadline 如何中断与恢复

在浏览器中,performWorkUntilDeadlinerequestIdleCallback 调用,并传入一个 deadline 对象,包含 deadline.timeRemaining()。示意流程如下:

┌──────────────────────────────────────────────────────────────────┐
│             浏览器空闲 → requestIdleCallback(performWork)        │
└──────────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────────────┐
│ performWork(deadline):                                            │
│   currentDeadline = now() + deadline.timeRemaining()              │
│   workLoopConcurrent()                                            │
│   if (firstTask !== null) { scheduleIdleCallback(performWork) }   │
└──────────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────────────┐
│ workLoopConcurrent:                                               │
│   while (firstTask) {                                             │
│     if (task.expirationTime <= now()) { // 过期,立即执行 }       │
│     else if (shouldYield()) { // 时间片用尽,停止循环 }             │
│       scheduleIdleCallback(performWork); return;                   │
│     } else { // 执行一个工作单元 }                                  │
│       runTaskUnit();                                               │
│     }                                                              │
│   }                                                                │
│   // 队列空或执行完成,不再调度                                     │
└──────────────────────────────────────────────────────────────────┘
  • 中断条件shouldYield()true
  • 恢复时机:任务尚未完成时,workLoopConcurrent 内部主动调用 scheduleHostCallback(即 requestIdleCallback),把剩余任务延后到下一空闲周期执行。

任务优先级调度流程图解

为了更直观地理解 Scheduler 的任务调度流程,下面用 ASCII 图示分别说明普通任务(未过期)与过期任务的处理逻辑。

┌────────────────────────────────────────────────────────────┐
│                    调度者视角:scheduleCallback             │
│                                                              │
│  1. 调用 scheduleCallback(priority, callback)                 │
│  2. 计算 expirationTime = now() + timeoutForPriority(priority)│
│  3. 将新任务插入环形链表,按 expirationTime 排序               │
│  4. 如果没有 pendingCallback ,则调用 requestIdleCallback     │
└────────────────────────────────────────────────────────────┘

                   ↓                 ↑
                   ↓  下一空闲周期   │
                   ↓                 │

┌────────────────────────────────────────────────────────────┐
│               浏览器空闲 → 调用 performWorkUntilDeadline    │
└────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────────┐
│                 workLoopConcurrent 开始执行                │
│                                                            │
│   while (firstTask) {                                      │
│     currentTask = firstTask                                │
│     if (currentTask.expirationTime <= now()) {             │
│       // 过期任务:立即执行,无需检查 shouldYield()        │
│       removeTask(currentTask)                               │
│       currentTask.callback(currentTask.expirationTime)      │
│       continue                                              │
│     }                                                       │
│     if (shouldYield()) {      // 时间片用尽                  │
│       scheduleIdleCallback(performWorkUntilDeadline)         │
│       return                                                 │
│     }                                                       │
│     // 尚有时间片:执行一个工作单元                          │
│     removeTask(currentTask)                                 │
│     contCallback = currentTask.callback(currentTask.expirationTime) │
│     if (typeof contCallback === 'function') {               │
│       scheduleCallback(currentTask.priorityLevel, contCallback)      │
│     }                                                       │
│   }                                                         │
│                                                            │
│   // 队列空,停止调度                                       │
└────────────────────────────────────────────────────────────┘
  • 上图展示了当 performWorkUntilDeadline 被调用后的内部逻辑。
  • 过期任务会绕过 shouldYield() 检查,即使帧时间不足也要立即执行,以避免实时交互死锁。
  • 非过期任务先检查 shouldYield(),若时间片不足,则暂停当前循环并安排下一帧继续。

基于 Scheduler 实现简易任务调度示例

下面再给出一个完整的小示例,将多个不同优先级的任务添加到队列,并观察其执行顺序。

import React, { useEffect } from 'react';
import { View, Text } from 'react-native';
import {
  unstable_scheduleCallback as scheduleCallback,
  unstable_NormalPriority as NormalPriority,
  unstable_UserBlockingPriority as UserBlockingPriority,
  unstable_LowPriority as LowPriority,
  unstable_IdlePriority as IdlePriority,
} from 'scheduler';

export default function SchedulerDemo() {
  useEffect(() => {
    console.log('当前时间:', Date.now());

    // 用户阻塞任务:优先级较高
    scheduleCallback(UserBlockingPriority, () => {
      console.log('1. 用户阻塞任务执行(优先级 2)');
    });

    // 普通任务:优先级 3
    scheduleCallback(NormalPriority, () => {
      console.log('2. 普通任务执行(优先级 3)');
    });

    // 低优先级任务:优先级 4
    scheduleCallback(LowPriority, () => {
      console.log('3. 低优先级任务执行(优先级 4)');
    });

    // 空闲任务:优先级 5
    scheduleCallback(IdlePriority, () => {
      console.log('4. 空闲任务执行(优先级 5)');
    });

    // 同步任务:优先级 1,会立即执行
    scheduleCallback(ImmediatePriority, () => {
      console.log('0. 同步任务执行(优先级 1)');
    });
  }, []);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>查看控制台输出,观察执行顺序</Text>
    </View>
  );
}

运行结果(Console)

当前时间:1620000000000
0. 同步任务执行(优先级 1)
1. 用户阻塞任务执行(优先级 2)
2. 普通任务执行(优先级 3)
3. 低优先级任务执行(优先级 4)
4. 空闲任务执行(优先级 5)
  • 同步任务(ImmediatePriority) 最先执行,不经过时间切片检查。
  • 随后 UserBlockingPriority 任务、NormalPriority 任务、LowPriority 任务、IdlePriority 任务按优先级依次执行。
  • 如果其中某个任务内部耗时较长,则可能会被后续更高优先级任务打断(如果它们尚未过期且到来)。

常见误区与优化建议

  1. 误区:unstable_scheduleCallback 会立即执行

    • 只有 ImmediatePriority 任务会同步执行;其余任务都需要等待浏览器空闲或下一帧时间片才会运行。
  2. 误区:shouldYield 返回 true 即不执行任何任务

    • shouldYield 只是表明当前帧剩余时间不足,如果任务已过期(expirationTime <= now()),仍会立即执行,跳过让出逻辑。
  3. 优化:合理拆分任务粒度

    • 当一个任务逻辑十分庞大(如大规模循环、复杂计算)时,应主动拆成多个子任务,避免一次执行耗时过长。
    • 使用 unstable_scheduleCallback 在循环内部分片,确保 shouldYield() 能及时生效。
  4. 优先级选取示例

    • 用户输入点击事件 等敏感交互要使用 UserBlockingPriority,避免输入迟滞。
    • 数据轮询预加载 可使用 LowPriority,不抢占重要更新的执行。
    • 日志、分析、缓存清理 等极低优先任务,用 IdlePriority,只有在完全空闲时才执行。
  5. 结合 React Concurrent Mode

    • 在 React 18+ 并发模式下,Scheduler 与 React Fiber 调度紧密结合,startTransition 会将更新标记为“可中断”的低优先级更新(通常映射到 NormalPriority)。
    • 不要在 render 中做过度阻塞主线程的操作,否则会影响并发更新的效果。

总结与学习建议

本文从以下几个方面深度解析了 React 的调度系统 Scheduler:

  • 核心概念:任务优先级、时间切片、让出逻辑。
  • 主要 APIunstable_scheduleCallbackunstable_shouldYieldPriority Levels 等。
  • 源码架构:HostConfig、内部环形链表、过期时间排序、工作循环(同步/并发模式)。
  • 典型示例:如何调度一个耗时计算并结合 shouldYield 分片执行;多优先级任务执行顺序;自定义任务调度示例。
  • 常见误区与优化建议:正确理解让出与过期任务、拆分任务粒度、结合 React 并发模式等。

要进一步掌握 Scheduler,推荐以下学习路径:

  1. 阅读官方源码

    • 仔细阅读 node_modules/scheduler/src/ 中的 Scheduler.jsSchedulerHostConfig*.jsSchedulerImplementation.js 文件。
    • 理解各个模块之间如何协作:HostConfig 提供环境 API、Implementation 负责队列与执行、Scheduler.js 暴露给用户的接口。
  2. 调试与实验

    • 在浏览器或 React Native 环境下,多写几个示例,观察不同优先级任务互相打断的行为。
    • 在 Chrome DevTools 或 React Native Debugger 中使用 Timeline/Profiler 模式,查看帧率与任务执行切片情况。
  3. 结合 React Fiber 调度

    • 阅读 React 源码中的 ReactFiberWorkLoop.js,了解 React 如何调用 Scheduler 来安排组件更新。
    • 尝试在自定义 Hook 或组件中使用 startTransition 标记“可延迟更新”,观察界面响应变化。
  4. 关注社区 RFC

    • React 团队会在 GitHub 上发布 Scheduler 相关的 RFC(例如“支持优先级更细分”或“改进时间切片”),可定期跟踪。
    • 参与社区讨论,有助于更快掌握新特性与最佳实践。

通过本文以及后续持续的源码阅读与实验,你将具备深入理解并高效使用 React 调度系统的能力,并能在复杂应用中通过合理安排任务优先级来优化性能与用户体验。

最后修改于:2025年05月29日 11:20

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日