はじめに
create-react-appでは、Web Workerを導入するのが困難(※1)でした。
Viteの場合はどうやるか?気になったので調べてみると、
Web Workerの読み込みをサポートしているので、簡単に実現できました。
※1. できないことはないですが、トリッキーな方法でしか実現できませんでした
- publicフォルダにWorkerのJSファイルを配置して読み込む(TypeScriptで書けない)
- ejectしてからWebPackの設定に worker-loader または worker-plugin を追加する
- react-app-rewired を使ってWebPackの設定を無理やり書き換える
- WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する
参考:
create-react-app(TypeScript)で作成したアプリにWeb Workerを導入する方法
サンプルプログラム
※「実行結果は・・・」はCSSアニメーションで左右に動いています。時間がかかる処理をWeb Workerで実行した場合、画面はブロックされません。
作成したソースはこちら
https://github.com/murasuke/react_vite_web_worker
GitHub Pagesのページ
https://murasuke.github.io/react_vite_web_worker/
作成手順
プロジェクト作成
- viteでReactプロジェクトを作成
$ npm create vite@latest react_vite_web_worker -- --template react-ts
$ cd react_vite_web_worker
$ code .
時間がかかるテスト関数を作成
そのまま実行すると、メインスレッド(画面の再描画)をブロックする関数を作成します
/**
* 時間がかかる処理(iterations:300で2秒前後)
* @param iterations
* @returns random()の合計
*/
export const blockingFunc = (iterations: number): number => {
console.log(`\titerations: ${iterations} * 1,000,000 loop`);
let result = 0;
for (let i = 0; i < iterations; i++) {
for (let j = 0; j < 1_000_000; j++) {
result += Math.random();
}
}
console.log(`\tresult:${result}`);
// randomの合計を返す
return result;
};
Web Workerから呼び出す
- Web Workerでイベントを受信後、2秒したら完了を返すテスト処理
import { blockingFunc } from './blockingFunc';
self.addEventListener('message', (e) => {
const iterations = Number.parseInt(String(e.data));
// 時間がかかる処理
const result = blockingFunc(iterations);
return result;
});
export default {};
ReactからWeb Workerを呼び出す
- Reactコンポーネントの
useEffect()
でWeb Workerを読み込む - ボタンクリックでWeb Workerの処理を呼び出す
import { useEffect, useRef } from 'react';
import Worker from './worker?worker'; // ?workerをつける
import { blockingFunc } from './blockingFunc';
import './App.css';
function App() {
const roopCount = 300;
const workerRef = useRef<Worker | null>(null);
useEffect(() => {
workerRef.current = new Worker(); // worker読み込み
workerRef.current.onmessage = (event) => {
const data = event.data;
console.log('メインスレッドで受信:', data);
};
return () => {
workerRef.current?.terminate();
};
}, []);
const handleClickWorker = () => {
if (workerRef.current) {
console.log('start blockingFunc() in web worker');
workerRef.current.postMessage(roopCount);
console.log('end blockingFunc() in web worker');
}
};
const handleClickSync = async () => {
if (workerRef.current) {
console.log('start blockingFunc()');
const result = blockingFunc(roopCount);
console.log(`end blockingFunc(): ${result}`);
}
};
return (
<div>
<button onClick={() => handleClickWorker()}>
時間がかかる関数をWebWorkerで非同期的に実行
</button>
<br />
<button onClick={() => handleClickSync()}>
時間がかかる関数を同期的に実行
</button>
<div className="return">実行結果はDevToolsのConsoleに出力されます。</div>
</div>
);
}
export default App;
CSSにアニメーション効果を追加
画面描画がブロックされていることがわかるように、「実行結果は・・・」を左右にアニメーションさせる。
@keyframes return {
50% {
left: 200px;
}
100% {
left: 0px;
}
}
.return {
width: 320px;
position: relative;
left: 0px;
top: 0;
animation-name: return;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease;
}
動作確認
$ npm run dev
VITE v5.1.5 ready in 191 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
- コンソールを開いてから、ボタンをクリックすると各処理の流れを追うことができます
- App.tsxからの呼び出し
- Web Workerで実行
- 2秒後にApp.tsxで処理結果の受け取り
-
Web Worker
で実行した場合は、画面のアニメーションが動き続けます -
同期的
に実行した場合、計算が終わるでアニメーションが固まります
おまけ GitHub Pagesにデプロイ
vite.config.ts
にbase:
を追加
react_vite_web_worker
の部分は、リポジトリ名です
export default defineConfig({
base: process.env.GITHUB_PAGES ? 'react_vite_web_worker' : './',
package.json
の`build'を変更
-
dist
をdocs
にコピー
"build": "tsc && vite build && cp -r dist docs",
GitHub Pagesで公開するための設定
- ①
Setting
⇒ ②Pages
をクリック - ③公開するブランチ(main or master)と、公開するディレクトリ
docs
を選択してSave
をクリック
ビルドとデプロイ
- ビルドの際
docs
ディレクトリが作成されることを確認(公開用)
$ npm run build
- デプロイ
コミットしてから、通常通りpushを行う
$ git push
GitHub Pagesで公開されたことを確認
数分待ってからhttps://murasuke.github.io/react_vite_web_worker/
へアクセスすると、GitHub Pagesで公開されたことが確認できる