本章主要研究,透過 Browser Web Workers API 改善 React 大型運算阻塞導致瀏覽器渲染幀數降低,進而促進使用者體驗。
最後筆者實驗性專案
- ReactWorkerContextPool(Github
其他較為流行的參考專案
- alewin/useWorker(Github
- dai-shi/react-hooks-worker(Github
在瀏覽器給予的環境下,透過 JavaScript 來控制元素間的互動方式,只要用幾個語法,取得元素後就可以開始控制畫面上的元素。
現今發展的網頁前端框架透過自身的設計模式、演算法能力來解決本身直接操作元素時造成的效能問題。
解決了動態渲染的問題,但依舊難以解決背後真實運算所要花費的成本,最後導致盡可能減少前端做大型運算來降低負擔。
前端框架確實降低了渲染成本問題,但要解決本身瀏覽器遇到大型運算時,卻是很無解,而這跟本身應用設計者可能也會有相關係。
大型運算問題一直存在於瀏覽器設計之中,瀏覽器 JavaScript 開始運作時為單一執行緒,所有同步行為的事情都必須仰賴上一個事情做完才能繼續下去,導致同步行為的大型運算阻塞,較為輕量的事情反而被拋在後頭等待著不能做。
但你知道的,瀏覽器除了負責運算,還要找時間渲染啊!
例如以下結果(Logo 每 100ms 會轉動一點點角度)
上述補足基礎知識後,來看看瀏覽器如何解決這樣的行為
在網頁使用者體驗上,通常運算與結果不是最重要的,而是畫面的流暢度,不論在使用者的「輸入」、「點擊」甚至是「捲軸滑動」都可能一點點的不順暢而導致心情不愉快,所以必然要被解決的事情就是「增加畫面每幀數的穩定」。
上文提到,網頁前端框架已經在進行幀數的穩定度問題,但依舊難以解決大型運算,說到底運行時的執行緒(Thread)還是只有一個。
那可能會思考,既然一個執行緒沒辦法流暢運行,那麼多增加幾個呢?讓這些大型運算放到另一個地方去計算不就好了嗎?
Good Point!這似乎就是要抓住的點了,「多執行緒」。
有些瀏覽器很早就開始進行這方面嘗試,而且越來越穩定,支援度也越來越高,就是文章開頭提到的「Web Workers API」。
Worker 會產生真正 OS 層級的執行緒,細心的開發者或許會擔心同步問題。
不過 worker 會十分注意和其他執行緒溝通的狀況,不會去存取非執行緒安全的元件,如 DOM ,而且資料的傳遞也都序列化 (serialized) ,所以說很難會發生同步問題。
瀏覽器盡可能讓執行緒(Thread)不去干涉污染到環境(Share Data),例如多執行緒常發生的(Race Condition),在 Web Workers 中可以說是(Thread-Safe)因為盡可能降低了程式可以觸碰到的地方。
如何建立 Worker 與基礎知識就不在此篇說明,可以至 MDN 文件中查看,主要以 React 如何使用為主。
React x Web Workers
開發會使用 Create React App 建立專案基礎架構,並另外嘗試包裝 ReactWorkerPool 成可行性的模組未來發布 npm 使用。
首先看看 React 中如何使用 Web Workers
此 computing.js 約為 150ms 左右,每一幀數為 16ms 的畫面,150 就導致約 10 幀的畫面消失了,這是滿嚴重的問題,不過只要透過 Web Workers 就可以降低至 2ms,當然原因是因為將運算交給另一個執行緒了,這時我們就完成最基本的「多執行緒」應用。
這就好像每 16ms 時,負責繪製畫面的人就會來詢問一下有沒有要繪製的東西,但因為還忙著在做運算,還來不及運算畫面要更動的變化,所以只好先跳過,再等下一次的 16ms 時來詢問,人眼一秒 60 幀(1000/60)所謂60fps 就是這麼來的了。
單執行緒示意圖
多執行緒示意圖
但 Web Workers 中只有一種方式建立另一個執行緒,就是透過同域名的檔案 URL 然後執行它,也就是一定要同網域的檔案,但是為了模組通用性,如果使用的只是一段程式碼時該怎麼解決呢?
參考了幾個較為流行通用的 React Web Workers 開源專案後,發現有個思維是使用 Blob 搭配 URL.createObjectURL()
產生的臨時網址。
可以改為以下內容
重點是這一段
const _createBlobObjectURL = (code: string) => {
const blob = new Blob([`${code}`], { type: "text/javascript" });
const url = URL.createObjectURL(blob);
return url;
};
直接將純文字程式碼內容放入 Blobl 容器中,再透過 createObjectURL 建立網址類似 blob:http://網頁網域/564baac5-f52f-4f22-9fec-e50eb1bbff2,建立好後就能將此網址當成臨時檔案網址使用。
好了以後再嘗試使用這段程式實測差異
會發現在點擊後,計算的過程都不需要阻塞畫面渲染了。
如果您有興趣探討更多關於實作細節,可以透過以下幾個專案來研究
最後筆者實驗性專案
- ReactWorkerContextPool(Github
其他較為流行的參考專案
- alewin/useWorker(Github
- dai-shi/react-hooks-worker(Github
我是懷恩,對於計算機充滿好奇且熱愛