使用 useWorker 的多线程 React App

使用 useWorker 在单独的线程中处理昂贵且阻塞 UI 的任务。

众所周知,Javascript 是一种单线程语言。所以,如果我们做任何昂贵的任务,它都会阻塞 UI 交互。并且用户需要等到它完成才能执行任何其他操作,这会带来糟糕的用户体验。

为了克服和执行这些任务,Javascript 有一个名为Web Workers的解决方案,它允许在不阻塞用户界面的情况下在 Web 浏览器中执行昂贵的任务,并使用户体验非常流畅。

让我们先看看什么是网络工作者。

网络工作者

Web Worker 是一个在后台运行而不影响用户界面的脚本,因为它在一个单独的线程而不是主线程中运行。所以它不会对用户交互造成任何阻塞。Web Worker 主要用于在 Web 浏览器中执行昂贵的任务,例如对大量数据进行排序、CSV 导出、图像处理等。

参考上图,我们可以看到一个昂贵的任务是由一个工作线程并行执行的,而不会阻塞主线程。当我们在主线程中执行相同的操作时,会导致 UI 阻塞。

React 中的 Concurrent 模式呢?

React并发模式不会并行运行任务。它将非紧急任务转移到立即执行紧急任务。它使用相同的主线程来处理它。

正如我们在下图中看到的,紧急任务是使用上下文切换来处理的。例如,如果一个表正在渲染一个大型数据集并且用户试图搜索某些内容,React 会将任务切换为用户搜索并首先处理它,如下所示。

当为同一任务使用 worker 时,表格渲染在一个单独的线程中并行运行。

使用工人

useWorker是一个通过 React Hooks 在简单配置中使用 Web Worker API 的库。它支持在不阻塞 UI 的情况下执行昂贵的任务,支持承诺而不是事件侦听器,以及其他值得注意的功能

  1. 杀死工人的超时选项

  1. 远程依赖

  1. 可转让

  1. 工人身份

useWorker 是一个 3KB 的库

为什么 JavaScript 不内置 web worker?

在使用 javascript web worker 时,我们需要添加一些复杂的配置来设置多行代码的 worker。使用 useWorker,我们可以简化 worker 的设置。让我们在下面的代码块中看看两者之间的区别。

在 React App 中使用 JS Web Worker 时

// webworker.js
self.addEventListener("message", function(event) {
  var numbers = [...event.data]
  postMessage(numbers.sort())
});

//index.js
var webworker = new Worker("./webworker.js");
webworker.postMessage([3,2,1]);
webworker.addEventListener("message", function(event) {
    console.log("Message from worker:", event.data); // [1,2,3]
});

在 React App 中使用 useWorker 时

    // index.js
    const sortNumbers = numbers => ([...numbers].sort())
    const [sortWorker] = useWorker(sortNumbers);
    const result = await sortWorker([1,2,3])

正如我之前所说,useWorker()与普通的 javascript worker 相比,它简化了配置。

让我们与 React App 集成并执行高 CPU 密集型任务以查看实际效果useWorker()

快速开始

要添加useWorker()到 React 项目,请使用以下命令

yarn add @koale/useworker

安装包后,导入 useWorker()。

import { useWorker, WORKER_STATUS } from "@koale/useworker";

我们从库中导入 useWorker 和 WORKER_STATUS。useWorker()钩子返回 workerFn 和控制器。

  1. workerFn是允许使用 web worker 运行函数的函数。

  1. 控制器由状态和终止参数组成。status 参数返回 worker 的状态和用于终止当前运行的 worker 的 kill 函数。

让我们看一个useWorker()的例子。

使用useWorker()和主线程对大数组进行排序

首先创建一个SortingArray组件,添加如下代码

    //Sorting.js
    import React from "react";
    import { useWorker, WORKER_STATUS } from "@koale/useworker";
    import { useToasts } from "react-toast-notifications";
    import bubleSort from "./algorithms/bublesort";
    const numbers = [...Array(50000)].map(() =>
      Math.floor(Math.random() * 1000000)
    );
    function SortingArray() {
      const { addToast } = useToasts();
      const [sortStatus, setSortStatus] = React.useState(false);
      const [sortWorker, { status: sortWorkerStatus }] = useWorker(bubleSort);
      console.log("WORKER:", sortWorkerStatus);
      const onSortClick = () => {
        setSortStatus(true);
        const result = bubleSort(numbers);
        setSortStatus(false);
        addToast("Finished: Sort", { appearance: "success" });
        console.log("Buble Sort", result);
      };
      const onWorkerSortClick = () => {
        sortWorker(numbers).then((result) => {
          console.log("Buble Sort useWorker()", result);
          addToast("Finished: Sort using useWorker.", { appearance: "success" });
        });
      };
      return (
        <div>
          <section className="App-section">
            <button
              type="button"
              disabled={sortStatus}
              className="App-button"
              onClick={() => onSortClick()}
            >
              {sortStatus ? `Loading...` : `Buble Sort`}
            </button>
            <button
              type="button"
              disabled={sortWorkerStatus === WORKER_STATUS.RUNNING}
              className="App-button"
              onClick={() => onWorkerSortClick()}
            >
              {sortWorkerStatus === WORKER_STATUS.RUNNING
                ? `Loading...`
                : `Buble Sort useWorker()`}
            </button>
          </section>
          <section className="App-section">
            <span style={{ color: "white" }}>
              Open DevTools console to see the results.
            </span>
          </section>
        </div>
      );
    }
    export default SortingArray;

这里我们配置了 useWorker 并传递了 bubleSort 函数来使用 worker 执行昂贵的排序。

接下来,将以下代码添加到App.js组件并导入 SortingArray 组件。

    //App.js
    import React from "react";
    import { ToastProvider } from "react-toast-notifications";
    import SortingArray from "./pages/SortingArray";
    import logo from "./react.png";
    import "./style.css";
    let turn = 0;
    function infiniteLoop() {
      const lgoo = document.querySelector(".App-logo");
      turn += 8;
      lgoo.style.transform = `rotate(${turn % 360}deg)`;
    }
    export default function App() {
      React.useEffect(() => {
        const loopInterval = setInterval(infiniteLoop, 100);
        return () => clearInterval(loopInterval);
      }, []);
      return (
        <ToastProvider>
          <div className="App">
            <h1 className="App-Title">useWorker</h1>
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <ul>
                <li>Sorting Demo</li>
              </ul>
            </header>
            <hr />
          </div>
          <div>
            <SortingArray />
          </div>
        </ToastProvider>
      );
    }

我们在App.js组件中添加了 React 标志,它每100 毫秒旋转一次,以直观地表示执行昂贵任务时的阻塞和非阻塞 UI。

运行上面的代码时,我们可以看到两个按钮 Normal Sort 和 Sort using useWorker()

接下来,单击 Normal Sort 按钮在主线程中对数组进行排序。我们可以看到 React 徽标停止旋转几秒钟。由于排序任务阻塞了UI渲染,所以排序完成后logo又开始旋转了。这是因为两个任务都在主线程中处理。检查以下gif

动图

让我们使用chrome 性能记录来检查它的性能分析。

我们可以看到 Event: click 任务用了3.86 秒来完成这个过程,它阻塞了主线程 3.86 秒。

接下来,让我们尝试使用useWorker()选项进行排序。点击它的时候我们可以看到react logo还在不间断的旋转。由于 useWorker 在不阻塞 UI 的情况下在后台执行排序。这使得用户体验非常流畅。检查以下gif

动图

您还可以在控制台使用sortWorkerStatus.

让我们看看这种方法的性能分析结果

正如我们所看到的,第一张图片表示主线程中没有长时间运行的进程。在第二张图片中,我们可以看到排序任务由一个单独的工作线程处理。所以主线程没有阻塞任务。

您可以在以下沙箱中试用整个示例。

https://codesandbox.io/embed/useworker-sorting-example-041qhc

什么时候使用工人

  1. 图像处理

  1. 排序或处理大型数据集。

  1. CSV 或 Excel 导出大数据。

  1. 画布绘图

  1. 任何 CPU 密集型任务。

useWorker 的限制

  1. 网络工作者无权访问窗口对象和文档。

  1. 当 worker 正在运行时,我们不能再次调用它,直到它完成或被杀死。为了解决这个问题,我们可以创建两个或多个useWorker()钩子实例。

  1. Web Worker 无法返回该函数,因为响应已序列化。

  1. Web Workers 受限于最终用户机器的可用 CPU 内核和内存。

结论

Web Worker 允许在 React 应用程序中使用多线程来执行昂贵的任务而不会阻塞 UI。而 useWorker 允许在 React 应用程序中以简化的挂钩方法使用 Web Worker API。Worker 不应该被过度使用,我们应该只在必要时使用它,否则会增加管理 Worker 的复杂性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

产品大道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值