使用 useWorker 在單獨的執行緒中,處理昂貴且阻塞 UI 的任務。
眾所周知,Javascript 是一種單線程語言。所以,做任何昂貴的任務,它都會阻塞 UI 互動。用戶需要等到它完成,才能執行剩餘的其他操作,這會帶來糟糕的用戶體驗。
為了克服和執行這些任務,Javascript 有一個名為 Web Workers 的解決方案,它允許在 web 中執行消耗效能的任務時,瀏覽器不會阻塞用戶界面,使用戶體驗非常流暢。
本篇文章簡單介紹這個 hook。
原文出處:https://dev.to/nilanth/multi-threaded-react-app-using-useworker-gf8
Web Worker 是一個在後台執行而不影響用戶界面的腳本,因為它在一個單獨的線程而不是主線程中執行。所以它不會對用戶交互造成任何阻塞。 Web Worker 主要用於在 Web 瀏覽器中執行昂貴的任務,例如對大量資料進行排序、CSV 導出、圖像處理等。
參考上圖,我們可以看到一個昂貴的任務是由一個工作線程並行執行的,而不會阻塞主線程。當我們在主線程中執行相同的操作時,會導致 UI 阻塞。
React 並發模式 不會並行執行任務。它將非緊急任務轉移,接著立即執行緊急任務。它使用相同的主線程來處理。
正如我們在下圖中看到的,緊急任務是使用上下文切換來處理的。例如,如果一個表正在呈現一個大型資料集,並且用戶試圖搜尋某些內容,React 會將任務切換為用戶搜尋,並首先處理它,如下所示。
當為同一任務使用 worker 時,表格渲染在一個單獨的線程中並行執行。檢查下圖。
useWorker 是一個通過 React Hooks 在簡單配置中使用 Web Worker API 的函式庫。它支持在不阻塞 UI 的情況下執行昂貴任務,支援 promise 而不是事件監聽器,還有一些值得注意的功能:
結束超時 worker 的選項
遠程依賴
Transferable
Worker status
useWorker 是一個 3KB 的庫
在使用 javascript web worker 時,我們需要加入一些複雜的配置,來設置多行程式碼的 worker。使用 useWorker,我們可以簡化 worker 的設置。讓我們在下面的程式碼中,看看兩者之間的區別。
// webworker.js
self.addEventListener("message", function(event) {
var numbers = [...event.data]
postMessage(numbers.sort())
});
//index.js
var webworker = new Worker("./webworker.js");
webworker.postMessage([3,2,1]);
webworker.addEventListener("message", function(event) {
console.log("Message from worker:", event.data); // [1,2,3]
});
// index.js
const sortNumbers = numbers => ([...numbers].sort())
const [sortWorker] = useWorker(sortNumbers);
const result = await sortWorker([1,2,3])
正如我之前所說,與普通的 javascript worker 相比,useWorker()
簡化了配置。
讓我們與 React App 整合並執行高 CPU 密集型任務,來看看 useWorker() 的實際執行情況。
要將 useWorker() 加入到 React 專案,請使用以下命令
yarn add @koale/useworker
安裝套件後,導入 useWorker()。
import { useWorker, WORKER_STATUS } from "@koale/useworker";
我們從函式庫中導入 useWorker 和 WORKER_STATUS。 useWorker() 鉤子返回 workerFn 和 controller。
workerFn
是允許使用 web worker 執行函數的函數。2.controller 由 status 和 kill 參數組成。 status 參數返回 worker 的狀態和用於終止當前執行的 worker 的 kill 函數。
讓我們用一個例子來看看 useWorker()。
使用 useWorker() 和主線程對大型陣列進行排序
首先建立一個SortingArray元件,加入如下程式碼
//Sorting.js
import React from "react";
import { useWorker, WORKER_STATUS } from "@koale/useworker";
import { useToasts } from "react-toast-notifications";
import bubleSort from "./algorithms/bublesort";
const numbers = [...Array(50000)].map(() =>
Math.floor(Math.random() * 1000000)
);
function SortingArray() {
const { addToast } = useToasts();
const [sortStatus, setSortStatus] = React.useState(false);
const [sortWorker, { status: sortWorkerStatus }] = useWorker(bubleSort);
console.log("WORKER:", sortWorkerStatus);
const onSortClick = () => {
setSortStatus(true);
const result = bubleSort(numbers);
setSortStatus(false);
addToast("Finished: Sort", { appearance: "success" });
console.log("Buble Sort", result);
};
const onWorkerSortClick = () => {
sortWorker(numbers).then((result) => {
console.log("Buble Sort useWorker()", result);
addToast("Finished: Sort using useWorker.", { appearance: "success" });
});
};
return (
<div>
<section className="App-section">
<button
type="button"
disabled={sortStatus}
className="App-button"
onClick={() => onSortClick()}
>
{sortStatus ? `Loading...` : `Buble Sort`}
</button>
<button
type="button"
disabled={sortWorkerStatus === WORKER_STATUS.RUNNING}
className="App-button"
onClick={() => onWorkerSortClick()}
>
{sortWorkerStatus === WORKER_STATUS.RUNNING
? `Loading...`
: `Buble Sort useWorker()`}
</button>
</section>
<section className="App-section">
<span style={{ color: "white" }}>
Open DevTools console to see the results.
</span>
</section>
</div>
);
}
export default SortingArray;
這裡我們配置了 useWorker 並傳遞了 bubleSort 函數來使用 worker 執行昂貴的排序。
接下來,將以下程式碼加入到 App.js 元件,並導入 SortingArray 元件。
//App.js
import React from "react";
import { ToastProvider } from "react-toast-notifications";
import SortingArray from "./pages/SortingArray";
import logo from "./react.png";
import "./style.css";
let turn = 0;
function infiniteLoop() {
const lgoo = document.querySelector(".App-logo");
turn += 8;
lgoo.style.transform = `rotate(${turn % 360}deg)`;
}
export default function App() {
React.useEffect(() => {
const loopInterval = setInterval(infiniteLoop, 100);
return () => clearInterval(loopInterval);
}, []);
return (
<ToastProvider>
<div className="App">
<h1 className="App-Title">useWorker</h1>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<ul>
<li>Sorting Demo</li>
</ul>
</header>
<hr />
</div>
<div>
<SortingArray />
</div>
</ToastProvider>
);
}
我們已將 React 徽標加入到 App.js 元件,該組件每 100ms 旋轉一次,以直觀地表示執行昂貴任務時的阻塞和非阻塞 UI。
執行上面的程式碼時,我們可以看到兩個按鈕 Normal Sort 和 Sort using useWorker()。
接下來,單擊 Normal Sort 按鈕在主線程中對陣列進行排序。我們可以看到 React 徽標停止旋轉幾秒鐘。由於排序任務阻塞了UI渲染,所以排序完成後logo又開始旋轉了。這是因為兩個任務都在主線程中處理。檢查以下gif
讓我們使用 chrome 性能記錄 檢查其性能分析。
我們可以看到 Event: click 任務用了 3.86 秒 來完成這個過程,它阻塞了主線程 3.86 秒。
接下來,讓我們嘗試使用 useWorker() 選項進行排序。點擊它的時候我們可以看到 react logo 還在不間斷的旋轉。由於 useWorker 在不阻塞 UI 的情況下在後台執行排序。這使得用戶體驗非常流暢。檢查以下gif
您還可以使用 sortWorkerStatus
在控制台中看到 worker 狀態為 RUNNING、SUCCESS。
讓我們看看這種方法的性能分析結果
正如我們所看到的,第一張圖片表示主線程中沒有長時間執行的進程。在第二張圖片中,我們可以看到排序任務由一個單獨的工作線程處理。所以主線程沒有阻塞任務。
您可以在以下沙箱中試用整個範例。
1.圖像處理
排序或處理大資料集。
大資料CSV或Excel導出。
畫布繪圖
任何 CPU 密集型任務。
web worker 無權存取 window 物件和 document。
當 worker 正在執行時,我們不能再次呼叫它,直到它完成或被中止。為了解決這個問題,我們可以建立兩個或多個 useWorker() 鉤子實例。
Web Worker 無法返回函數,因為響應是序列化的。
Web Workers 受限於最終用戶機器的可用 CPU 內核和內存。
Web Worker 允許在 React 應用程式中使用多線程來執行昂貴的任務而不會阻塞 UI。而 useWorker 允許在 React 應用程式中以簡化的掛鉤方法使用 Web Worker API。 Worker 不應該被過度使用,我們應該只在必要時使用它,否則會增加管理 Worker 的複雜性。