2

postTask:React的杀手锏被浏览器原生实现了?

 2 years ago
source link: https://segmentfault.com/a/1190000040751671
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

大家好,我卡颂。

React这几年一直在完善的并发模式主要由以下两部分组成:

  • 基于fiber实现的可中断更新的架构
  • 基于调度器的优先级调度

可以说,从16年开始重构fiber架构到今年底(或明年初)React18发布正式版,这期间React团队大部分工作都是围绕这两点展开的。

如果现在告诉你,React呕心沥血多年实现的优先级调度,浏览器原生就支持,会不会很惊讶?

文章参考Building a Faster Web Experience with the postTask Scheduler

什么是优先级调度

假设,我们有个记录日志的脚本需要在页面初始化后执行:

initCriticalTracking();

调用栈火炬图如下:

可以看到,这是个执行了249.08ms的长任务,在执行期间浏览器会掉帧(表现为:浏览器卡顿)。

现在,我们将其包裹在优先级调度函数scheduler.postTask的回调函数中:

scheduler.postTask(() => initCriticalTracking());

长任务被分解为多个短任务:

在每个任务之间浏览器有机会重排、重绘,减少了掉帧的可能性。

这种根据任务优先级将任务拆解,分配执行时间的技术,就是优先级调度

scheduler.postTaskChrome实现的优先级调度API

scheduler.postTask属于试验功能,需要在 chrome://flags 中打开 #enable-experimental-web-platform-features

之前是如何实现优先级调度的

scheduler.postTask出现之前,通常使用浏览器提供的会在不同阶段调用的API模拟优先级调度,比如:

  • requestAnimationFrame(简称rAF)一般用来处理动画,会在浏览器渲染前触发
  • requestIdleCallback(简称rIC)在每一帧没有其他任务的空闲时间调用
  • setTimeoutpostMessageMessageChannel在渲染之间触发

React使用MessageChannel实现优先级调度,setTimeout作为降级方案。

但是,这些API毕竟都有本职工作。用他们实现的优先级调度比较粗糙。

基于此原因,postTask Scheduler诞生了。

postTask Scheduler的使用

scheduler.postTask有3种可选优先级:

优先级描述polyfill实现user-blocking最高优先级,可能会阻塞用户交互使用 MessageChannel 调度任务, setTimeout作为降级方案user-visible第二优先级,对用户可见,但不会阻塞用户交互。比如:渲染第二屏内容。这是默认优先级在 user-blocking 实现的基础上通过优先级队列控制background最低优先级,通常执行不紧急任务,例如记录日志使用 rIC 实现,setTimeout(0)作为降级方案

使用方式很简单,通过以下方式注册的回调函数会以默认优先级调度:

// 默认优先级
scheduler.postTask(() => console.log('Hello, postTask'));

你也可以指定优先级与执行延迟:

// 调用后延迟1秒执行,优先级最低
scheduler.postTask(() => console.log('Hello, postTask'), {
   delay: 1000,
   priority: 'background',
});

postTask建立在AbortSignal API上,所以我们可以取消尚在排队还未执行的回调函数。

通过使用TaskController API控制:

const controller = new TaskController('background');
window.addEventListener('beforeunload', () => controller.abort());
 
scheduler.postTask(() => console.log('Hello, postTask'), {
   signal: controller.signal,
});

同时,实验性的schedule.wait方法可以让我们轻松的等待某一时机后再执行任务。

比如,我们可以在页面加载完成后异步加载xxx.js

async function loadxxx() {
  // 等待事件被派发
  await scheduler.wait('myPageHasLoaded');
  return import('xxx.js');
}
 
// 页面加载后派发事件
window.dispatchEvent(new CustomEvent('myPageHasLoaded'));

以上代码被简化为postTaskevent配置项:

scheduler.postTask(() => import('xxx.js'), {
   event: 'myPageHasLoaded'
})

优先级调度可以应用在很多领域,比如:

  • 资源提前、延后请求
  • 第三方资源延迟加载

......

可以预见,未来这势必会增加前端编程复杂度。

就像曾经,当web应用复杂到一定程度时,出现了前端框架,开发者不用直接操作DOM

未来,当优先级调度复杂到一定程度时,一定也会出现集成解决方案,让开发者不用直接操作优先级

慢着,这不就是React现在在做的事么?

欢迎加入人类高质量前端框架研究群,带飞


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK