postTask:React的殺手鐗被瀏覽器原生實現了?

卡頌發表於2021-09-30

大家好,我卡頌。

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現在在做的事麼?

歡迎加入人類高質量前端框架研究群,帶飛

相關文章