UI スレッド(メインスレッド)で重い計算を行うと、ボタンが反応しなくなったりアニメーションが止まったりします。
Web Worker を使用して別スレッドで処理を実行し、UI のレスポンスを維持します。
Vite では特別な設定なしに Worker をインポートできます。
1. Worker ファイルの作成
src/workers/heavy-calculation.ts:
// 自己完結したコンテキストで動作するコード
self.onmessage = (e: MessageEvent<number>) => {
const num = e.data;
const result = fibonacci(num);
self.postMessage(result);
};
function fibonacci(n: number): number {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
2. メインスレッドからの呼び出し
メインスレッド(UI スレッド)から呼び出します。
Vite では new Worker コンストラクタと new URL を組み合わせて Worker を生成するのが標準的です。
const resultEl = document.getElementById('result');
const buttonEl = document.getElementById('btn-calc');
if (buttonEl) {
buttonEl.addEventListener('click', () => {
// Worker インスタンス生成
const worker = new Worker(new URL('./workers/heavy-calculation.ts', import.meta.url), {
type: 'module',
});
worker.onmessage = (e: MessageEvent<number>) => {
if (resultEl) {
resultEl.textContent = `Result: ${e.data}`;
}
worker.terminate(); // 使い終わったら破棄
};
worker.postMessage(40); // 計算開始
});
}
3. Comlink の活用
postMessage によるメッセージングは低レベルで扱いづらいため、Google 製のライブラリ comlink を使うと、Worker 内の関数を非同期関数として透過的に呼び出せるようになります。
npm install comlink
これにより、RPC のように直感的に記述できます。
src/workers/heavy-comlink.ts:
import * as Comlink from 'comlink';
function fibonacci(n: number): number {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const api = {
fibonacci,
};
// 型をエクスポートしておくとメインスレッド側で便利です
export type HeavyApi = typeof api;
Comlink.expose(api);
メインスレッド側:
import * as Comlink from 'comlink';
import type { HeavyApi } from '../workers/heavy-comlink'; // 型定義のみインポート
const worker = new Worker(new URL('./workers/heavy-comlink.ts', import.meta.url), {
type: 'module',
});
// Worker を Comlink でラップ (型引数を指定するとIDEの補完が効きます)
const api = Comlink.wrap<HeavyApi>(worker);
// 普通の非同期関数として呼び出せる
const result = await api.fibonacci(40);
console.log(result);