阅读 122

React学习笔记:调度器(Scheduler)- 时间切片

 react版本: 18 Alpha

浏览器每一帧需要执行的任务

切片调度

下面是react源码中的几种调度方式,有先后关系

方式一:isInputPending

参考文档:wicg.github.io/is-input-pe…

在运行需要显示某些内容的脚本时,开发人员今天需要做出判断。

如果脚本可能计算很长时间才能运行并且用户在发生这种情况时进行了某种输入,那么浏览器将需要等到脚本完成后才能分派输入事件。这会造成在响应输入事件之前有很长的延迟,用户体验并不是很好,因此开发人员通常会将长脚本任务分解成更小的块,以允许用户代理在块之间调度事件。每次脚本执行时,它都需要以某种方式发布一条消息,调用 requestAnimation 帧和 requestIdleCallback 的组合,或者采用其它方式来生成可以被调度的事情,之后它可以在空闲时再次被调用。即使在最好的情况下,脚本每次产生时也可能需要很多毫秒才能再次运行。所以不幸的是,这也不是一个很好的用户体验,因为部分初始的脚本被延迟了很久才执行,尽管他需要这么久的时间。

为了避免这种取舍,FacebookChromium 中提出并实现了 isInputPending() API,它可以提高网页的响应能力,但是不会对性能造成太大影响。

isInputPending api 的目标是它现在将允许开发人员消除这种权衡。不再完全屈服于用户代理,并且在屈服后必须承担一个或多个事件循环的成本,长时间运行的脚本现在可以运行到完成,同时仍然保持响应。

目前 isInputPending API 仅在 Chromium 的 87 版本开始提供,其他浏览器并未实现。 

方式二:setImmediate

方式一主要使用在Chromium引擎的浏览器中,方式二从设计上,优先考虑了IE的兼容性

该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。

IE支持

MessageChannel

Channel Messaging API的MessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据。

在以下示例中,您可以看到使用MessageChannel构造函数实例化了一个channel对象。当iframe加载完毕,我们使用MessagePort.postMessage方法把一条消息和MessageChannel.port2传递给iframe。handleMessage处理程序将会从iframe中(使用MessagePort.onmessage监听事件)接收到信息,将数据其放入innerHTML中。

var channel = new MessageChannel();
var para = document.querySelector('p');

var ifr = document.querySelector('iframe');
var otherWindow = ifr.contentWindow;

ifr.addEventListener("load", iframeLoaded, false);

function iframeLoaded() {
  otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
}

channel.port1.onmessage = handleMessage;
function handleMessage(e) {
  para.innerHTML = e.data;
} 
复制代码

setTimeout

给大家都懂,略过

切片时间

切片间隔时间是5ms,最大间隔时间是300ms,源码如下

// Scheduler periodically yields in case there is other work on the main
// thread, like user events. By default, it yields multiple times per frame.
// It does not attempt to align with frame boundaries, since most tasks don't
// need to be frame aligned; for those that do, use requestAnimationFrame.
let yieldInterval = 5;
let deadline = 0;

// TODO: Make this configurable
// TODO: Adjust this based on priority?
const maxYieldInterval = 300;
let needsPaint = false;
复制代码

切片和React相互关系

任务拆分

将调和阶段(Reconciler)递归遍历 VDOM 这个大任务分成若干小任务,每个任务只负责一个节点的处理。

  • workLoopSync or workLoopConcurrent

  • performUnitOfWork

任务挂起、恢复、终止

在新 workInProgress tree 的创建过程中,会同 currentFiber 的对应节点进行 Diff 比较,生成对应的falgs,同时也会复用和 currentFiber 对应的节点对象,减少新创建对象带来的开销。也就是说无论是创建还是更新、挂起、恢复以及终止操作都是发生在 workInProgress tree 创建过程中的。workInProgress tree 构建过程其实就是循环的执行任务和创建下一个任务。

挂起

当第一个小任务完成后,先判断这一帧是否还有空闲时间,没有就挂起下一个任务的执行,记住当前挂起的节点,让出控制权给浏览器执行更高优先级的任务。

恢复

在浏览器渲染完一帧后,判断当前帧是否有剩余时间,如果有就恢复执行之前挂起的任务。如果没有任务需要处理,代表调和阶段完成,可以开始进入渲染阶段。

终止

其实并不是每次更新都会走到提交阶段。当在调和过程中触发了新的更新,在执行下一个任务的时候,判断是否有优先级更高的执行任务,如果有就终止原来将要执行的任务,开始新的 workInProgressFiber 树构建过程,开始新的更新流程。这样可以避免重复更新操作

任务优先级

下列是源码中提供的任务优先级,除了无优先任务外,其它任务数值越小优先级越高

// 无优先级任务
export const NoPriority = 0;
// 立即执行任务
export const ImmediatePriority = 1;
// 用户阻塞任务
export const UserBlockingPriority = 2;
// 正常任务
export const NormalPriority = 3;
// 低优先级任务
export const LowPriority = 4;
// 空闲执行任务
export const IdlePriority = 5;
复制代码

文章分类
前端
文章标签