はじめに
以前作成したQRコード認識Reactコンポーネントを格好よくするため、上下に移動する「緑色のバー」を追加しました(CSS animation)。
※ ↓ 画像なのでわかりづらいですが「緑色のバー」が上下します。
ところが、バーの動きがガクガクになり、スムーズにアニメーションしてくれません。
QRコードの認識処理が描画処理をブロックするためです。(タイマーで定期的に認識処理を実行)
こういう場合はWeb Workerを利用すれば、バックグラウンドで認識処理行うことができます。
ところがcreate-react-appで作成したプログラムには、簡単には導入できないようです。
-
create-react-appがWeb Workerをサポートしていない
-
Web Workerは.jsファイルを要求するため、ejectしてからWebPackを設定する必要がある。
試行錯誤の上、eject無しに動く手段が見つかりました。
comlink-loaderを使うと、容易に導入できます
Web Workerで実行する処理をTypeScriptの通常のメソッドとして
書くことができます。
呼び出しも通常の非同期メソッド(postMessageは不要)として呼びだすことができる優れものです。
Web Workerを意識せず、メソッドの呼び出しとして処理できてしまいます!
comlink-loaderの組み込み手順
- ./src/worker フォルダに下記3ファイルを作成します
ファイル名 | 説明 |
---|---|
custom.d.ts | 型定義。worker.tsの型に合わせる(戻り値はPromis<>でラップする) |
index.ts | workerのインラインローダー。説明を読んでもよくわかりません・・・・ |
worker.ts | Web Workerに実行させる処理(function)定義 |
/* ./worker/custom.d.ts */
declare module 'comlink-loader!*' {
class WebpackWorker extends Worker {
constructor();
// Add any custom functions to this class.
// Make note that the return type needs to be wrapped in a promise.
processData(data: ImageData): Promise<QRCode>;
}
export = WebpackWorker;
}
/* ./worker/index.ts */
// eslint-disable-next-line
import Worker from 'comlink-loader!./worker'; // inline loader
export default Worker;
- QRコードの認識処理を記載します。
/* ./worker/worker.ts */
import jsqr, { QRCode } from 'jsqr';
export function processData(data: ImageData): QRCode {
// Process the data without stalling the UI
const qr = jsqr(data.data, data.width, data.height);
if (qr) {
console.log(qr.data);
return qr;
}
return null;
}
- 利用側ソース
Workerを生成して、Promiseを返す非同期メソッドとして呼び出すだけです。
(workerはレンダリング毎に生成されるのを防ぐためuseMemo()でキャッシュしています)
PostMessage()を使わず、普通のメソッドとしてWeb Workerが呼び出せてしまいます。
/* ./QRReader.tsx */
const QRReader: React.FC<QRReaderProps> = (props) => {
const worker = useMemo(() => new Worker(), [])
// ~~~ 途 中 略 ~~~
timerId.current = setInterval(() => {
context.drawImage(video.current, 0, 0, width, height);
const imageData = context.getImageData(0, 0, width, height);
worker.processData(imageData).then(qr => {
if (qr) {
console.log(qr.data);
if (props.showQRFrame) {
drawRect(qr.location.topLeftCorner, qr.location.bottomRightCorner);
}
if (props.onRecognizeCode) props.onRecognizeCode(qr);
}
});
}, props.timerInterval);
create-react-appで作ったアプリケーションとWeb Workerを組み合わせた場合に発生する技術的な課題について
Create React AppでWeb Workerを使うには からの引用です。Web Workerを使うのはかなりしんどいようです。
1. publicフォルダにWorkerのJSファイルを配置して読み込む
Web Workerは.js
ファイルを読み込むため、publicフォルダにworker.js
ファイルを別途作成して配置しておくか、worker.ts
を別途tscでビルドしてpublicフォルダに配置するようにする必要がある。
const worker = new Worker('worker.js');
worker.postMessage(`hoge`);
処理内容をTypeScriptで書きたい、別にビルドするのが面倒なので却下
2. ejectしてからWebPackの設定に worker-loader または worker-plugin を追加する
ejectしたくない、WebPackを直接使いたくないので却下
3. ejectせずに react-app-rewired を使ってWebPackの設定に worker-loader または worker-plugin を追加する。
WebPackを直接使いたくないので却下
4. WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する。
わからんでもないが、トリッキー過ぎるので却下
Is it possible to use load webworkers? #1277 で上記1.~4.の議論が行われていますが、結論が良く割りませんでした。