はじめに
React Hooksを使うと、非同期処理を比較的簡単に書くことができます。つまり、async/awaitをhooks内に隠蔽することができます。Web Workerを手軽に利用する有名なライブラリとしてcomlinkがありますが、WebWorkerとの通信は非同期であるためawaitをつけなければいけません。そこで、より手軽に扱えるReact Hooks専用のWeb Workerラッパーを紹介します。
react-hooks-worker
リポジトリはこちらです。
https://github.com/dai-shi/react-hooks-worker
このライブラリでは、Web Workerとの通信をstateを介して行うため、async/awaitをあまり意識する必要がないことが特徴です。
Web Workerを手軽に使うには、bundlerのサポートが重要です。各種プラグイン等がありますが、今回使用したのはwebpackのworker-pluginです。これにより、workerでも外部ライブラリが使えるようになります(本記事での使用例の紹介は無し)。
使用例
フィボナッチ数を計算する例を紹介します。まずは、workerの実装です。
// slow_fib.worker.js:
import { exposeWorker } from 'react-hooks-worker';
const fib = i => (i <= 1 ? i : fib(i - 1) + fib(i - 2));
exposeWorker(fib);
次に、これを利用するReactコンポーネントの実装です。
// app.js:
import React from 'react';
import { useWorker } from 'react-hooks-worker';
const createWorker = () => new Worker('./slow_fib.worker', { type: 'module' });
const CalcFib = ({ count }) => {
const { result, error } = useWorker(createWorker, count);
if (error) return <div>Error: {error}</div>;
return <div>Result: {result}</div>;
};
const App = () => (
<div>
<CalcFib count={5} />
</div>
);
これだけで、workerが使えるようになります。重い計算処理(かつ、結果が小さい場合)は、どんどんworkerにoffloadしましょう。
発展的な使い方
上記の例ではworkerの実装は単純な関数でしたが、実は、非同期関数やgeneratorでも動きます。列挙すると使えるパターンは下記になります。
- sync function
- async function
- sync generator function
- async generator function
参考までに、async generatorでフィボナッチ数の計算過程をゆっくりと出力するworker関数を載せます。
// fib-steps.worker.js
import { exposeWorker } from 'react-hooks-worker';
async function* fib(x) {
let x1 = 0;
let x2 = 1;
let i = 0;
while (i < x) {
yield `(calculating...) ${x1}`;
await new Promise(r => setTimeout(r, 100));
[x1, x2] = [x2, x1 + x2];
i += 1;
}
yield x1;
}
exposeWorker(fib);
workerの処理をプログレッシブに表示する場合などにこのパターンが使えるのではないでしょうか。
おわりに
本ライブラリではworkerは関数として表現されますが、comlinkは様々なオブジェクトをサポートしています。よく紹介されるのはworkerをclassとして実装する例ですが、このパターンを好む人がいることを知りました。react-hooks-workerでも様々なパターンをサポートすることが今後の改題の一つになりそうです。