jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

React 项目中使用 Web Worker #61

Open jtwang7 opened 1 year ago

jtwang7 commented 1 year ago

React 项目中使用 Web Worker

Web Worker 一般用法

主线程 index.js

// 创建一个 worker 
const worker = new Worker('./worker.js'); 
// 发送消息 
worker.postMessage('Hello World'); 
// 接受数据 
worker.onmessage = function(e) { 
    console.log(e.data); // 这里可以对接受到的数据进行一些处理 
    console.log('Message received from worker'); 
} 
// 终止 
worker worker.terminate(); 
// 错误处理 
worker.onerror = function(e) => { }

子线程 worker.js

onmessage = function (e) {
    // 一些复杂的计算或者耗时的事情
    const msg = `post-msg-${e.data}`;
    postMessage(msg);
    // 在 worker 线程内部可以是 close() 来关闭
}

在主线程中,使用 onmessage 和 posMessage() 必须挂在 worker 对象上。

而在 worker 线程中不需要这么做,因为在 worker 线程内部,worker 是有效的全局作用域

Web Worker 的局限性

React 中的使用

web worker 需要指定一个脚本的 URI,而在 React 的实际项目中,我们一般都会和打包工具一起使用(比如:webpack),这时如果直接引用文件,就会发生错误,React 项目会报以下错误:

// index.tsx
const worker = new Worker('./worker.ts')

// 👉 报错
Uncaught SyntaxError: Unexpected token '<'

解决方法 1⃣️ - worker-loader 使用方法参考 worker-loader 若和 ts 搭配使用,则还需要参考web worker with ts

👉 App.tsx 中的内联写法

import Worker from "worker-loader!./Worker.js";

👉 或者通过 webpack 配置引用: *App.tsx**

import Worker from "./xxx.worker.ts";

const worker = new Worker();
worker.postMessage({ a: 1 });
worker.onmessage = function (event) {};
worker.addEventListener("message", function (event) {});

webpack.config.js

yarn add ts-loader worker-loader

module.exports = {
module: {
rules: [
{
test: /\.worker\.ts$/,
use: [
{
loader: 'worker-loader',
options: {
inline: true,
},
},
{
loader: 'ts-loader',
},
],
}
]
}
}
  • test:规定要检测的文件后缀
  • use:插件配置
  • loader:所使用的 webpack loader

解决方法 2⃣️ - worker-plugin 使用 plugin 插件而不是内联到 webpack,具体使用方法参考 worker-plugin


解决方法 3⃣️ - blob 参考How to Use Web Workers in React ❌ 缺点:无法引入第三方库

  1. 创建 worker 文件 fibo.worker.js 并以函数形式模块化默认导出
    // eslint-disable-next-line import/no-anonymous-default-export
    export default () => {
    // eslint-disable-next-line no-restricted-globals
    self.onmessage = (e) => {
    const msg = e.data;
    // use msg to do something ...
    postMessage(result);
    };
    };

在文件开头添加下述代码可以避免一些 eslint 检测

// eslint-disable-next-line import/no-anonymous-default-export
// eslint-disable-next-line no-restricted-globals
  1. React 项目中无法直接引用文本路径,因此需要借助 React 自身的打包流程引入 worker.js 文件。WorkerBuilder 的作用就是将 React import 的文件流通过 Blob 对象 var aBlob = new Blob( array, options ); 转为 url 形式,在传递给 web worker 使用。
//woker-builder.js
export default class WorkerBuilder extends Worker {
  constructor(worker) {
    const code = worker.toString(); // 文件流字符串化
    const blob = new Blob([`(${code})()`]); // 将 指令:运行 fibo.worker.js 中导出的函数 转为 Blob 对象
    super(URL.createObjectURL(blob)) // super() 接收 url 并实例化(new)父类 Worker,new Worker 最终会返回 Worker 对象
  }
}

❇️ 为什么要将文件以函数形式导出,并构造成 (${code})() IIFE 形式? Worker 实例化接受一个 url 文件地址,其会依照路径找到相应文件,并自主运行该文件的内容。 code 对应了 ./worker.js 文件中的代码片段,由于 ./worker.js 中代码以函数形式导出,所以在执行时,就需要 IIFE 格式包裹它:(${code})() 所以并非一定要构造成 IIFE 形式,我们只需要保证代码片段可以被正确执行就行,IIFE 是模块化导出场景下最简便的执行方式。

import WorkerBuilder from './worker/woker-builder.js';
import Worker from './worker/fibo.worker.js';
instance = new WorkerBuilder(Worker);

🌈 现阶段最佳实践 4⃣️ - import.meta.url

使用前提:基于 webpack5 构建项目

以 react 为例,如果是基于最新的 CRA 脚手架搭建的项目,即基于 webpack5 的项目,可以比较便捷地生成 Worker。 该方法相比 blob 最大的好处在于:其访问 worker.js 模块时仍在 React 项目依赖关系内,因此可以访问到其他第三方库。

const worker = new Worker(new URL("./worker.js", import.meta.url));
worker.postMessage({
  name: "Lucas",
});
worker.onmessage = ({ data: { name } }) => {
  console.log(name);
};

import.meta 是一个内置在 ES 模块内部的对象,包含了对应模块的所有信息。 import.meta.url 表示一个模块在浏览器和 Node.js 的绝对路径。

该特性属于 es2020 的一部分,webpack5 才支持。

上述方法可以实现 React 项目中,基于 import.meta.url 提供的模块绝对路径,访问到相对路径下的文件,进而实例化 Web Worker 对象。

或者可以把 worker.js 固定放在 public 文件夹下,默认打包后 public 下的文件是固定放在根路径下,可以通过 http://localhost:3000/worker.js 找到

./worker.js

import _ from 'lodash'

onmessage = function (e) {
  const { data: msg } = e;
  // doSomething ...
  postMessage(result);
};

export {}

如果是基于 webpack4 的项目,请参考并借助上述其他方法。


注意事项

❇️ 在使用了web worker 的页面,多次操作后,页面出现卡顿或者直接卡死 检查多次操作后,页面是否存在多个 web worker。修改创建 web worker 的逻辑,使页面不要同时出现多个 web worker。web worker 在页面销毁后要主动终止。