Open jtwang7 opened 1 year ago
Web Worker
主线程 index.js
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
worker.js
onmessage = function (e) { // 一些复杂的计算或者耗时的事情 const msg = `post-msg-${e.data}`; postMessage(msg); // 在 worker 线程内部可以是 close() 来关闭 }
在主线程中,使用 onmessage 和 posMessage() 必须挂在 worker 对象上。 而在 worker 线程中不需要这么做,因为在 worker 线程内部,worker 是有效的全局作用域
在主线程中,使用 onmessage 和 posMessage() 必须挂在 worker 对象上。
而在 worker 线程中不需要这么做,因为在 worker 线程内部,worker 是有效的全局作用域
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
worker-loader
👉 App.tsx 中的内联写法
import Worker from "worker-loader!./Worker.js";
👉 或者通过 webpack 配置引用: *App.tsx**
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
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
✅ 解决方法 2⃣️ - worker-plugin 使用 plugin 插件而不是内联到 webpack,具体使用方法参考 worker-plugin
worker-plugin
✅ 解决方法 3⃣️ - blob 参考How to Use Web Workers in React ❌ 缺点:无法引入第三方库
blob
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
WorkerBuilder
var aBlob = new Blob( array, options );
//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 是模块化导出场景下最简便的执行方式。
(${code})()
Worker
code
./worker.js
import WorkerBuilder from './worker/woker-builder.js'; import Worker from './worker/fibo.worker.js'; instance = new WorkerBuilder(Worker);
🌈 现阶段最佳实践 4⃣️ - import.meta.url
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 的绝对路径。
import.meta
该特性属于 es2020 的一部分,webpack5 才支持。
上述方法可以实现 React 项目中,基于 import.meta.url 提供的模块绝对路径,访问到相对路径下的文件,进而实例化 Web Worker 对象。
或者可以把 worker.js 固定放在 public 文件夹下,默认打包后 public 下的文件是固定放在根路径下,可以通过 http://localhost:3000/worker.js 找到
import _ from 'lodash' onmessage = function (e) { const { data: msg } = e; // doSomething ... postMessage(result); }; export {}
export {}
如果是基于 webpack4 的项目,请参考并借助上述其他方法。
❇️ 在使用了web worker 的页面,多次操作后,页面出现卡顿或者直接卡死 检查多次操作后,页面是否存在多个 web worker。修改创建 web worker 的逻辑,使页面不要同时出现多个 web worker。web worker 在页面销毁后要主动终止。
React 项目中使用 Web Worker
Web Worker
一般用法主线程
index.js
子线程
worker.js
Web Worker
的局限性React 中的使用
web worker 需要指定一个脚本的 URI,而在 React 的实际项目中,我们一般都会和打包工具一起使用(比如:webpack),这时如果直接引用文件,就会发生错误,React 项目会报以下错误:
✅ 解决方法 1⃣️ -
worker-loader
使用方法参考 worker-loader 若和 ts 搭配使用,则还需要参考web worker with ts👉 App.tsx 中的内联写法
👉 或者通过 webpack 配置引用: *
App.tsx
**webpack.config.js
✅ 解决方法 2⃣️ -
worker-plugin
使用 plugin 插件而不是内联到 webpack,具体使用方法参考 worker-plugin✅ 解决方法 3⃣️ -
blob
参考How to Use Web Workers in React ❌ 缺点:无法引入第三方库fibo.worker.js
并以函数形式模块化默认导出在文件开头添加下述代码可以避免一些 eslint 检测
worker.js
文件。WorkerBuilder
的作用就是将 React import 的文件流通过 Blob 对象var aBlob = new Blob( array, options );
转为 url 形式,在传递给 web worker 使用。❇️ 为什么要将文件以函数形式导出,并构造成
(${code})()
IIFE 形式?Worker
实例化接受一个 url 文件地址,其会依照路径找到相应文件,并自主运行该文件的内容。code
对应了./worker.js
文件中的代码片段,由于./worker.js
中代码以函数形式导出,所以在执行时,就需要 IIFE 格式包裹它:(${code})()
所以并非一定要构造成 IIFE 形式,我们只需要保证代码片段可以被正确执行就行,IIFE 是模块化导出场景下最简便的执行方式。🌈 现阶段最佳实践 4⃣️ -
import.meta.url
以 react 为例,如果是基于最新的 CRA 脚手架搭建的项目,即基于 webpack5 的项目,可以比较便捷地生成 Worker。 该方法相比
blob
最大的好处在于:其访问worker.js
模块时仍在 React 项目依赖关系内,因此可以访问到其他第三方库。import.meta
是一个内置在 ES 模块内部的对象,包含了对应模块的所有信息。import.meta.url
表示一个模块在浏览器和 Node.js 的绝对路径。上述方法可以实现 React 项目中,基于
import.meta.url
提供的模块绝对路径,访问到相对路径下的文件,进而实例化 Web Worker 对象。./worker.js
export {}
导出空对象,表明该文件是一个模块,与 React 建立依赖关系。如果是基于 webpack4 的项目,请参考并借助上述其他方法。
注意事项
❇️ 在使用了web worker 的页面,多次操作后,页面出现卡顿或者直接卡死 检查多次操作后,页面是否存在多个 web worker。修改创建 web worker 的逻辑,使页面不要同时出现多个 web worker。web worker 在页面销毁后要主动终止。