您现在的位置是:首页 >学无止境 >React源码解读(一)scheduler.js网站首页学无止境

React源码解读(一)scheduler.js

kristyzhangwx 2025-03-14 12:01:02
简介React源码解读(一)scheduler.js

react/packages/scheduler/src/forks/Scheduler.js

这个文件主要是 React 调度器(Scheduler)的实现代码,用于管理任务的调度和执行,确保不同优先级的任务能够在合适的时机被处理。以下是对该文件中关键部分的详细介绍:

1. 任务(Task)类型定义

export opaque type Task = {
  id: number,
  callback: Callback | null,
  priorityLevel: PriorityLevel,
  startTime: number,
  expirationTime: number,
  sortIndex: number,
  isQueued?: boolean,
};
  • id:任务的唯一标识符,通过 taskIdCounter 递增生成,用于维护任务的插入顺序。
  • callback:任务要执行的回调函数,可能为 null,表示任务已被取消。
  • priorityLevel:任务的优先级,如 ImmediatePriorityUserBlockingPriority 等。
  • startTime:任务的开始时间。
  • expirationTime:任务的过期时间,用于判断任务是否超时。
  • sortIndex:任务在队列中的排序索引,用于在堆中排序。
  • isQueued:可选属性,用于标记任务是否已入队,在性能分析时使用。

PriorityLevel 是在 react/packages/scheduler/src/SchedulerPriorities.js 文件中定义的类型,具体内容如下:

export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

PriorityLevel 是一个联合类型,它的值可以是 012345。同时,为了方便使用,还定义了一些常量来表示这些优先级:

  • NoPriority:值为 0,表示无优先级。
  • ImmediatePriority:值为 1,表示立即执行的最高优先级。
  • UserBlockingPriority:值为 2,表示用户阻塞优先级,通常用于需要立即响应用户操作的任务。
  • NormalPriority:值为 3,表示普通优先级。
  • LowPriority:值为 4,表示低优先级。
  • IdlePriority:值为 5,表示空闲优先级,通常用于在浏览器空闲时执行的任务。

2. 获取当前时间的函数

let getCurrentTime: () => number | DOMHighResTimeStamp;
const hasPerformanceNow =
  // $FlowFixMe[method-unbinding]
  typeof performance === 'object' && typeof performance.now === 'function';

if (hasPerformanceNow) {
  const localPerformance = performance;
  getCurrentTime = () => localPerformance.now();
} else {
  const localDate = Date;
  const initialTime = localDate.now();
  getCurrentTime = () => localDate.now() - initialTime;
}
  • 首先检查浏览器是否支持 performance.now() 方法,如果支持,则使用该方法获取当前时间。
  • 如果不支持,则使用 Date.now() 方法,并通过减去初始时间来模拟相对时间。

3. 最大 31 位整数常量

// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
var maxSigned31BitInt = 1073741823;
  • 定义了一个最大 31 位整数常量 maxSigned31BitInt,用于表示 V8 引擎在 32 位系统中的最大整数大小。

4. 任务队列和定时器队列

// Tasks are stored on a min heap
var taskQueue: Array<Task> = [];
var timerQueue: Array<Task> = [];
  • taskQueue:存储即将执行的任务,使用最小堆(min heap)数据结构进行排序,确保优先级高的任务先执行。
  • timerQueue:存储延迟执行的任务,同样使用最小堆排序,根据任务的开始时间进行排序。

5. 任务计数器和当前任务信息

// Incrementing id counter. Used to maintain insertion order.
var taskIdCounter = 1;

var currentTask = null;
var currentPriorityLevel = NormalPriority;

// This is set while performing work, to prevent re-entrance.
var isPerformingWork = false;

var isHostCallbackScheduled = false;
var isHostTimeoutScheduled = false;

var needsPaint = false;
  • taskIdCounter:任务的 ID 计数器,每次创建新任务时递增,用于生成唯一的任务 ID。
  • currentTask:当前正在执行的任务。
  • currentPriorityLevel:当前的优先级级别,默认为 NormalPriority
  • isPerformingWork:标志位,用于表示是否正在执行任务,防止重入。
  • isHostCallbackScheduled:标志位,表示是否已经调度了宿主回调。
  • isHostTimeoutScheduled:标志位,表示是否已经调度了宿主超时。
  • needsPaint:标志位,表示是否需要进行重绘。

6. 本地 API 引用

// Capture local references to native APIs, in case a polyfill overrides them.
const localSetTimeout = typeof setTimeout === 'function' ? setTimeout : null;
const localClearTimeout =
  typeof clearTimeout === 'function' ? clearTimeout : null;
const localSetImmediate =
  typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
  • 捕获本地的 setTimeoutclearTimeoutsetImmediate 函数的引用,以防这些函数被 polyfill 覆盖。

7. 推进定时器函数

function advanceTimers(currentTime: number) {
  // Check for tasks that are no longer delayed and add them to the queue.
  let timer = peek(timerQueue);
  while (timer !== null) {
    if (timer.callback === null) {
      // Timer was cancelled.
      pop(timerQueue);
    } else if (timer.startTime <= currentTime) {
      // Timer fired. Transfer to the task queue.
      pop(timerQueue);
      timer.sortIndex = timer.expirationTime;
      push(taskQueue, timer);
      if (enableProfiling) {
        markTaskStart(timer, currentTime);
        timer.isQueued = true;
      }
    } else {
      // Remaining timers are pending.
      return;
    }
    timer = peek(timerQueue);
  }
}
  • 该函数用于检查定时器队列中是否有任务已经达到开始时间,如果有,则将其从定时器队列中移除,并添加到任务队列中。
  • 如果任务已经被取消(callbacknull),则直接从定时器队列中移除。
  • 如果任务的开始时间小于等于当前时间,则将其从定时器队列中移除,并添加到任务队列中,同时更新任务的排序索引。
  • 如果任务的开始时间大于当前时间,则表示剩余的定时器任务仍在等待,直接返回。

总的来说,这个文件通过定义任务类型、管理任务队列和定时器队列,以及实现一些辅助函数,实现了一个任务调度器,用于管理 React 中的异步任务。

在 React 中,任务调度是一个非常重要的机制,它允许 React 有效地管理和执行不同优先级的任务,以确保用户界面的流畅性和响应性。以下是基于你提供的代码片段及 React 的整体设计来详细描述 React 任务调度过程:

1. 初始化和状态设置

首先,会有一些初始化的操作,比如设置一些标志位。在你提供的代码片段中,有 needsPaint = false; 这样的操作,它可能用于标记是否需要进行重绘。

2. 检查消息循环状态

通过 isMessageLoopRunning 这个标志来判断消息循环是否正在运行。如果正在运行,会执行以下操作:

if (isMessageLoopRunning) {
  const currentTime = getCurrentTime();
  // Keep track of the start time so we can measure how long the main thread
  // has been blocked.
  startTime = currentTime;

这里会获取当前时间 currentTime 并记录为 startTime,目的是为了后续测量主线程被阻塞的时长。

3. 执行任务

在消息循环运行的情况下,会尝试执行任务。这里会使用 flushWork 函数来执行具体的工作:

let hasMoreWork = true;
try {
  hasMoreWork = flushWork(currentTime);
} finally {
  if (hasMoreWork) {
    // If there's more work, schedule the next message event at the end
    // of the preceding one.
    schedulePerformWorkUntilDeadline();
  } else {
    isMessageLoopRunning = false;
  }
}
  • flushWork 函数会执行当前的任务,并且返回一个布尔值 hasMoreWork 表示是否还有更多的任务需要执行。
  • 如果 hasMoreWorktrue,说明还有任务需要执行,那么会调用 schedulePerformWorkUntilDeadline 函数来安排下一次的任务执行。
  • 如果 hasMoreWorkfalse,则将 isMessageLoopRunning 标志设置为 false,表示消息循环结束。

4. 调度下一次任务执行

schedulePerformWorkUntilDeadline 函数的实现会根据不同的环境有所不同:

let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
  // Node.js and old IE.
  schedulePerformWorkUntilDeadline = () => {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== 'undefined') {
  // DOM and Worker environments.
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  • 在 Node.js 和旧版 IE 环境中,如果 localSetImmediate 函数存在,会使用 localSetImmediate 来调度下一次的任务执行。
  • 在 DOM 和 Worker 环境中,如果 MessageChannel 可用,会使用 MessageChannel 来调度下一次的任务执行。

在这段代码中,当处于 DOM 和 Worker 环境且 MessageChannel 可用时,会使用 MessageChannel 来调度下一次的任务执行。下面详细解释这部分代码的逻辑和原因:

代码片段

} else if (typeof MessageChannel !== 'undefined') {
  // DOM and Worker environments.
  // We prefer MessageChannel because of the 4ms setTimeout clamping.
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () => {
    port.postMessage(null);
  };
}

详细解释

  1. 条件判断

    } else if (typeof MessageChannel !== 'undefined') {
    

    这里使用 typeof MessageChannel !== 'undefined' 来检查当前环境是否支持 MessageChannel。如果支持,说明当前环境可能是 DOM 或 Worker 环境,因为 MessageChannel 主要在这些环境中可用。

  2. 创建 MessageChannel 实例

    const channel = new MessageChannel();
    const port = channel.port2;
    
    • MessageChannel 是 HTML5 中引入的一个用于在不同上下文(如主线程和 Web Worker 之间)进行通信的 API。
    • 当创建一个 MessageChannel 实例时,它会有两个端口 port1port2,这两个端口可以互相通信。这里将 port2 赋值给变量 port,后续会使用这个端口来发送消息。
  3. 设置消息监听

    channel.port1.onmessage = performWorkUntilDeadline;
    
    • port1 注册了一个 onmessage 事件处理函数 performWorkUntilDeadline。这意味着当 port2 发送消息时,port1 会接收到这个消息,并触发 performWorkUntilDeadline 函数的执行。
  4. 定义调度函数

    schedulePerformWorkUntilDeadline = () => {
      port.postMessage(null);
    };
    
    • schedulePerformWorkUntilDeadline 是一个函数,用于调度 performWorkUntilDeadline 的执行。
    • 当调用 schedulePerformWorkUntilDeadline 时,它会通过 port.postMessage(null)port1 发送一个消息(这里的消息内容为 null,只是一个触发信号)。
    • 由于 port1 监听了 onmessage 事件,当收到消息后,就会执行 performWorkUntilDeadline 函数,从而完成任务的调度。

使用 MessageChannel 的原因

// We prefer MessageChannel because of the 4ms setTimeout clamping.

注释中提到了使用 MessageChannel 的原因是因为 setTimeout 的 4ms 限制。在浏览器环境中,setTimeout 有一个最小延迟时间的限制,通常是 4ms(不同浏览器可能略有不同)。这意味着如果使用 setTimeout 来调度任务,即使设置的延迟时间为 0,实际的延迟也可能会达到 4ms 左右。而使用 MessageChannel 可以避免这个问题,能够更精确地控制任务的调度时间,从而提高性能。

综上所述,在 DOM 和 Worker 环境中,如果 MessageChannel 可用,通过创建 MessageChannel 实例并利用其端口之间的消息传递机制,可以更精确地调度下一次的任务执行。

5. 任务优先级管理

在 React 中,不同的任务会有不同的优先级。比如,用户交互相关的任务(如

点击事件)通常会有较高的优先级,而一些数据更新或渲染任务可能会有较低的优先级。React 会根据任务的优先级来决定哪些任务先执行,哪些任务后执行。

6. 空闲时间利用

React 会利用浏览器的空闲时间来执行一些低优先级的任务。当主线程没有其他高优先级任务需要处理时,React 会尝试执行这些低优先级的任务,以提高性能和用户体验。

总的来说,React 的任务调度过程是一个复杂而高效的机制,它通过不断地检查消息循环状态、执行任务、调度下一次任务执行,并根据任务的优先级来管理任务的执行顺序,从而确保用户界面的流畅性和响应性。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。