HTML5 新特性: Web Worker 的创建与使用(webpack + TS 环境)

HTML5 新特性: Web Worker 的创建与使用(webpack + TS 环境)

前言

我们都知道 JS 一直都是单线程的语言,并透过事件循环来提供异步操作的方法避免堵塞。

本篇来介绍一个 HTML5 新出的特性 Web Worker,它能够真正的为 JS 带来多线程的特性的一套规范,后续还有更多的妙用。本篇主要介绍基础的使用方式,以及在 TS 环境下的一些配置问题。

本篇不会对 Worker 的一些定义做过多的描述,主要偏重在实践并写到真实项目里头,相关的概念还是先参考一些博客会好一些

正文

1. 基础使用

首先第一版我们先搞最基础的版本,也就是直接调用浏览器最原始的 API 的使用方式

const worker = new Worker('sample.js')

实际上他会以同源的方式请求一个 js 文件,并加载作为 Worker 来执行

1.1 Worker 定义

接下来我们要定义一个 Worker 线程内要执行的脚本

  • /src/workers/test1.worker.js
const PREFIX_WORKER1 = '[Worker1]';

self.onmessage = (event) => {
  const msg = event.data;
  console.log(`${PREFIX_WORKER1} receive msg in worker: ${msg}`);

  const greeting = `${msg} from test1.worker.js`;
  postMessage(greeting);
};

1.2 Worker 使用

下面我们看看项目内的用法

  • /src/layouts/Test1.tsx
const Test1 = () => {
  const createWorker = () => {
    const worker = new Worker('workers/test1.worker.js');

    worker.onmessage = (event) => {
      const msg = event.data;
      console.log(`${PREFIX_TEST1} worker.onmessage: ${msg}`);
      worker.terminate();
      console.log(`${PREFIX_TEST1} worker finished`);
    };

    const msg = 'Hello World';
    console.log(`${PREFIX_TEST1} worker.postMessage: ${msg}`);
    worker.postMessage('Hello World');
  };

  return (
    <div>
      <h2>Test1 - Basic Worker</h2>
      <button onClick={createWorker}>createWorker</button>
    </div>
  );
};

本质上就是 1 个创建和 3 个函数的操作

  1. 创建 Worker
const worker = new Worker('workers/test1.worker.js');
  1. 发送消息
worker.postMessage('Hello World');
  1. 监听消息
worker.onmessage = (event) => {/* ... */}
  1. 终止 Worker
worker.terminate();

实现效果如下

2. worker-loader

第二种场景我们还可以在 webpack 中引入 Worker 的特性,甚至用起来比原生的更优雅

2.1 webpack 配置

首先是 webpack.config.js 配置要加上

module.exports = {
  // ...
  module: {
    rules: [
     {
        test: /\.worker\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: ['worker-loader', 'ts-loader'],
      },
    ]
  },
  // ...
}

2.2 Worker 定义

接下来我们在定义一个新的 Worker

  • /src/workers/test2.worker.ts
const PREFIX_WORKER2 = '[Worker2]';

self.onmessage = (event) => {
  const msg = event.data;
  console.log(`${PREFIX_WORKER2} receive msg in worker: ${msg}`);

  const greeting = `${msg} from test2.worker.ts`;
  postMessage(greeting);
};

2.3 Worker 使用

然后我们就可以像引入一个模块一样引入一个 Worker 脚本

  • /src/layouts/Test2.tsx
import React from 'react';

import { PREFIX_TEST2 } from '@utils/prefixs';
import Worker from '@workers/test2.worker.ts';

const Test2 = () => {
  const createWorker = () => {
    const worker = new Worker();

    worker.onmessage = (event) => {
      const msg = event.data;
      console.log(`${PREFIX_TEST2} worker.onmessage: ${msg}`);
      worker.terminate();
      console.log(`${PREFIX_TEST2} worker finished`);
    };

    const msg = 'Hello World';
    console.log(`${PREFIX_TEST2} worker.postMessage: ${msg}`);
    worker.postMessage('Hello World');
  };

  return (
    <div>
      <h2>Test2 - Worker Loader</h2>
      <button onClick={createWorker}>createWorker</button>
    </div>
  );
};

export default Test2;

与第一次的差别在于

import Worker from '@workers/test2.worker.ts';

const worker = new Worker();

也就是说接下来 webpack 可以将 Worker 用的脚本一并处理打包起来,而不需要我们额外再去管理 worker 的部署

2.4 TS 环境下的配置

然而上述的写法 TS 会给出一大堆报错,这时候需要补上一些配置

  • tsconfig.json
{
  "compilerOptions": {
    "lib": ["WebWorker", "ScriptHost", "DOM"],
    "allowJs": false,
  }
}

除此之外,默认的 Worker 构造函数是需要传入一个脚本路径名的,但是在 webpack 下我们直接 import 然后就使用无参数构造函数了,所以我们需要额外建立一个类型声明

  • /src/types/worker.d.ts
declare module '*.worker.ts' {
  class WebpackWorker extends Worker {
    constructor();
  }
  export default WebpackWorker;
}

最终效果如下

3. 实践示例:计时器

最后我们摆上一个用 Web Worker 做的时钟范例

3.1 Worker 定义

  • /src/workers/test3.worker.ts
type MessageType = 'RESET' | 'SKIP';

let count = 0;
let skipOnce = false;

self.onmessage = (e: MessageEvent<{ type: MessageType }>) => {
  const { type } = e.data;
  switch (type) {
    case 'RESET':
      count = 0;
      break;
    case 'SKIP':
      skipOnce = true;
      break;
  }
};

const SEC = 1000;
setInterval(() => {
  if (skipOnce) {
    skipOnce = false;
  } else {
    count++;
  }
  self.postMessage({ currentTime: new Date(), count });
}, SEC);

3.2 Worker 使用

  • /src/layouts/Test3.tsx
import React, { useEffect, useRef, useState } from 'react';

import Worker from '@workers/test3.worker.ts';

const useWorker = () => {
  const [currentTime, setCurrentTime] = useState(new Date());
  const [count, setCount] = useState(0);

  const workerRef = useRef(null);

  useEffect(() => {
    const worker = new Worker();

    worker.onmessage = (e) => {
      const { currentTime, count } = e.data;
      setCurrentTime(currentTime);
      setCount(count);
    };

    workerRef.current = worker;
  }, []);

  const reset = () => {
    workerRef.current?.postMessage({ type: 'RESET' });
    setCount(0);
    console.log(workerRef.current);
  };

  const skip = () => {
    workerRef.current?.postMessage({ type: 'SKIP' });
    console.log(workerRef.current);
  };

  const terminate = () => {
    workerRef.current?.terminate();
    console.log(workerRef.current);
  };

  return [
    { currentTime, count },
    { reset, skip, terminate },
  ];
};

const Test3 = () => {
  const [{ currentTime, count }, { reset, skip, terminate }] = useWorker();

  return (
    <div>
      <h2>Test3 - Timer by Worker</h2>
      <h3>currentTime: {currentTime.toString()}</h3>
      <h3>count: {count}</h3>
      <button onClick={reset}>reset</button>
      <button onClick={skip}>skip</button>
      <button onClick={terminate}>terminate</button>
    </div>
  );
};

export default Test3;

最终效果

结语

Web Worker 的最大好处就是在于他真正的创建一个独立于 JS 主线程的新线程,能真正实现并行运行。

其他资源

参考连接

TitleLink
Web Worker 使用教程 - 阮一峰https://www.ruanyifeng.com/blog/2018/07/web-worker.html
worker-loaderhttps://cloud.tencent.com/developer/section/1477547
The import path cannot end with a ‘.ts‘ extensionhttps://blog.csdn.net/peade/article/details/117534994
Typescript error “Cannot write file xxx because it would overwrite input filehttps://blog.csdn.net/weixin_43459866/article/details/116356968

完整代码示例

https://github.com/superfreeeee/Blog-code/tree/main/front_end/html/html5_web_worker

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值