大家好,我卡頌。
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.postTask是Chrome
實現的優先順序排程API。
scheduler.postTask屬於試驗功能,需要在 chrome://flags 中開啟 #enable-experimental-web-platform-features
之前是如何實現優先順序排程的
在scheduler.postTask
出現之前,通常使用瀏覽器提供的會在不同階段呼叫的API模擬優先順序排程,比如:
requestAnimationFrame
(簡稱rAF
)一般用來處理動畫,會在瀏覽器渲染前觸發requestIdleCallback
(簡稱rIC
)在每一幀沒有其他任務的空閒時間呼叫setTimeout
、postMessage
、MessageChannel
在渲染之間觸發
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'));
以上程式碼被簡化為postTask
的event
配置項:
scheduler.postTask(() => import('xxx.js'), {
event: 'myPageHasLoaded'
})
總結
優先順序排程可以應用在很多領域,比如:
- 資源提前、延後請求
- 第三方資源延遲載入
......
可以預見,未來這勢必會增加前端程式設計複雜度。
就像曾經,當web
應用複雜到一定程度時,出現了前端框架,開發者不用直接操作DOM
。
未來,當優先順序排程複雜到一定程度時,一定也會出現整合解決方案,讓開發者不用直接操作優先順序
。
慢著,這不就是React
現在在做的事麼?
歡迎加入人類高質量前端框架研究群,帶飛