React + ViteでWeb Workerを使う
React + ViteのプロジェクトでWeb Workerを使う方法をメモします。
背景
React + Viteのプロジェクトで、重い処理を挟むとレンダリングプロセスがブロックされるという事態が発生しました。(JavaScriptはシングルスレッドなので非同期処理を使っても他のプロセスがブロックされうる)
レンダリングプロセスをブロックしないためにWeb Workerを使ってマルチスレッド実装をすることにしました。
ReactでのWeb Worker実装方法を検索するとconst worker = new Worker("./worker.js");
みたいな感じでworkerのスクリプトファイルを読み込ませる記事がたくさんヒットしたのですが、手元のVite環境だと動きませんでした。
Vite環境でWeb Workerを使う他の方法を見つけたのでメモを残します。
手順
1. Web Workerの実装
まずWeb Workerを以下のように実装します。
ここでは単に2秒間待機して、完了したら文字列をメインスレッドに返すようにします。
self.addEventListener('message', () => {
// ここで重たい処理を実行
// 例: 2秒間待機
setTimeout(() => {
// 処理が完了したら、結果をメインスレッドに送信
self.postMessage('Heavy process completed');
}, 2000);
});
export default {}
export default {}
でexportしておきます。
2. ReactコンポーネントからWeb Workerを使用
コンポーネントからは以下のようにWorkerを使用します。
import { FC, useEffect, useRef } from "react";
import tryWorker from "./try.worker?worker"; // ?workerをつける
const App: FC = () => {
const workerRef = useRef<Worker | null>(null);
useEffect(() => {
workerRef.current = new tryWorker(); // worker読み込み
workerRef.current.onmessage = (event) => {
const data = event.data;
console.log("メインスレッドで受信:", data);
};
return () => {
workerRef.current?.terminate();
};
}, []);
const handleClick = () => {
if (workerRef.current) {
console.log("メインスレッドで送信");
workerRef.current.postMessage("開始");
}
};
return (
<div>
<h1>React + ViteでWeb Workerを使ったマルチスレッド処理</h1>
<button onClick={handleClick}>重い処理を開始</button>
</div>
);
};
export default App;
まずimport tryWorker from "./try.worker?worker";
でworkerをimportしておきます。
ここで?worker
をsuffixにつけておきます。(https://vitejs.dev/guide/features.html#import-with-query-suffixes 参照)
次にworkerRef.current = new tryWorker();
でworkerを読み込ませます。(workerのスクリプトファイルへのパスを渡す必要はありません)
後はworkerRef.current.postMessage("開始")
のようにworkerにメッセージを送信してあげることでworkerに処理をさせることができます。
以上でReact + ViteのプロジェクトでWeb Workerを使用することができました!
参考
Discussion