React Scheduling

React Scheduling 是 React v16 后 Reconciler (Fiber Reconciler) 的一部分, 指的是 React 何时确定何时应执行 work 的过程。

问题背景

这个问题看 Dan 在 JSConf 2018 的 Part 1 就能够理解.

由于浏览器中 JS 的单线程环境,需要面对以下问题:

  1. 长时间运行的任务会导致帧丢失。 所以需要确保所有任务都较小,并且可以在几毫秒内完成,以便可以在一帧内运行它们。

  2. 不同的任务具有不同的优先级。 优先考虑用户输入总体上会带来更好的体验。所以需要一种定义顺序并相应安排任务的方法。

React 采用了两种模式来解决上诉问题:

  • Concurrent React(时间切片)。 在渲染期间暂停和增量更新。类似与 Git Commit。

  • Scheduler。 在浏览器中注册具有不同优先级的回调。

Concurrent React 还在 Experimental 阶段

Concurrent Features 在 React 18 已经正式发布

Scheduler

Scheduler。 通用协作主线程 Scheduler 是由 React Core 团队开发的,可以在浏览器中注册具有不同优先级的回调。

Scheduler 目前支持的优先级:

https://github.com/facebook/react/blob/4c6470cb3b821f3664955290cd4c4c7ac0de733a/packages/scheduler/src/SchedulerPriorities.js

React 中的采用的 Scheduler 的优先级:

https://github.com/facebook/react/blob/4c6470cb3b821f3664955290cd4c4c7ac0de733a/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js#L57-L63

  • Immediate 用于需要同步运行的任务。

  • UserBlocking (250 毫秒超时)用于应因用户交互(例如,单击按钮)而运行的任务。

  • Normal (5s 超时),让您不必感到瞬间更新。

  • Low (10 秒超时)用于可以推迟但最终仍必须完成的任务(例如,分析通知)。

  • Idle (无超时)用于根本不需要运行的任务(例如,隐藏的屏幕外内容)。

制定超时时间以避免饥饿(starvation)问题。 即使有很多高优先级的工作要做,低优先级的工作也可以连续运行。

Scheduler 会将所有已注册的回调存储在按到期时间(注册回调的时间加上优先级超时)的顺序排列的列表中。 然后,Scheduler 将自己注册一个回调,该回调在浏览器绘制下一帧之后运行。 在此回调中,Scheduler 将执行尽可能多的已注册回调,直到需要渲染下一帧为止。

当前实现中,Scheduler 通过在 requestAnimationFrame() 回调中使用 postMessage()实现的。

Scheduler 的使用场景

仅仅在需要优化的时候才需要 Scheduler。 如果当前 APP 运行流畅,完全没有必要。

使用 Scheduler

import {
  unstable_next,
  unstable_LowPriority,
  unstable_scheduleCallback,
} from "scheduler";

// 高消耗的 API,对其降级处理
function sendDeferredAnalyticsNotification(value) {
  unstable_scheduleCallback(unstable_LowPriority, function () {
    sendAnalyticsNotification(value);
  });
}

function SearchBox(props) {
  const [inputValue, setInputValue] = React.useState();

  function handleChange(event) {
    const value = event.target.value;

    setInputValue(value);
    unstable_next(function () {
      props.onChange(value);
      sendAnalyticsNotification(value);
    });
  }

  return <input type="text" value={inputValue} onChange={handleChange} />;
}

Scheduler 的局限

使用 Scheduler,可以控制执行回调的顺序,并且可以在 Concurrent 模式下直接使用。

目前仍有两个限制:

  • 资源斗争。 Scheduler 尝试使用所有可用资源。 如果 Scheduler 的多个实例在同一线程上运行并争夺资源,则会导致问题。我们需要确保应用程序的所有部分都将使用相同的 Scheduler 实例。

  • 使用户定义的任务与浏览器工作保持平衡。 由于 Scheduler 在浏览器中运行,因此它只能访问浏览器公开的 API。 无法影响比如 Html 的渲染、回收周期。

Scheduling API in the Browser 就是为了解决该问题

参考

最后更新于