使

使用 useWorker 的多线程 React App

2025-05-25

使用 useWorker 的多线程 React App

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

众所周知,JavaScript 是一门单线程语言。因此,如果我们执行任何耗时的任务,它都会阻塞 UI 交互。用户需要等到它完成后才能执行其他操作,这会给用户体验带来很差。

为了克服和执行此类任务,Javascript 有一个名为Web Workers 的解决方案,它允许在 Web 浏览器中执行昂贵的任务而不会阻塞用户界面并使 UX 非常流畅。

首先让我们看看什么是 Web Worker。

Web Workers

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

工作线程

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

React 中的并发模式怎么样?

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

如下图所示,紧急任务会使用上下文切换来处理。例如,如果某个表正在渲染一个大型数据集,而用户尝试搜索某些内容,React 会将任务切换为用户搜索,并优先处理该任务,如下所示。

任务切换

当使用 Worker 执行同一任务时,表格渲染会在单独的线程中并行运行。请查看下图。

反应工人

使用Worker

useWorker是一个使用 React Hooks 以简单配置使用 Web Worker API 的库。它支持在不阻塞 UI 的情况下执行高开销任务,支持使用 Promise 替代事件监听器,其他值得关注的功能包括:

  1. 超时选项可终止工作进程
  2. 远程依赖项
  3. 可转让
  4. 工人状态

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())
});
Enter fullscreen mode Exit fullscreen mode

//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]
});
Enter fullscreen mode Exit fullscreen mode

在 React App 中使用 useWorker 时

    // index.js

    const sortNumbers = numbers => ([...numbers].sort())
    const [sortWorker] = useWorker(sortNumbers);

    const result = await sortWorker([1,2,3])
Enter fullscreen mode Exit fullscreen mode

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

让我们与 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 工作程序运行函数的函数。
  2. 控制器由status和kill参数组成,status参数返回worker的状态,kill函数用于终止当前正在运行的worker。

让我们通过一个例子来看一下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;
Enter fullscreen mode Exit fullscreen mode

这里我们配置了 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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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

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

接下来,点击“普通排序”按钮,在主线程中对数组进行排序。我们可以看到 React 徽标停止旋转了几秒钟。由于排序任务阻塞了 UI 渲染,因此排序完成后,徽标会恢复旋转。这是因为两个任务都在主线程中处理。请查看以下 gif 动图。

正常排序

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

表现正常

我们可以看到Event:click任务花费了3.86秒来完成该过程,并且它阻塞了主线程3.86秒。

接下来,我们尝试使用useWorker()选项进行排序。点击它时,我们可以看到 React 徽标仍在旋转,没有中断。这是因为 useWorker 在后台执行排序,不会阻塞 UI。这使得用户体验非常流畅。请查看以下 gif

工人分类

您还可以使用 来在控制台中查看工作者状态为RUNNINGSUCCESSsortWorkerStatus

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

主线程

工作线程

我们可以看到,第一幅图表示主线程中没有长时间运行的进程。第二幅图则显示排序任务由单独的工作线程处理。因此,主线程中没有阻塞任务。

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

何时使用 worker

  1. 图像处理
  2. 对大型数据集进行排序或处理。
  3. 包含大量数据的 CSV 或 Excel 导出。
  4. 画布绘画
  5. 任何 CPU 密集型任务。

使用限制Worker

  1. Web 工作者无权访问窗口对象和文档。
  2. 当 Worker 运行时,我们无法再次调用它,直到它完成或被终止。为了解决这个问题,我们可以创建两个或更多个useWorker()hook 实例。
  3. 由于响应已被序列化,因此 Web Worker 无法返回该函数。
  4. Web Worker 受到最终用户机器可用 CPU 核心和内存的限制。

结论

Web Worker 允许在 React 应用中使用多线程执行高开销任务,而不会阻塞 UI。而 useWorker 允许在 React 应用中以简化的 hooks 方式使用 Web Worker API。Worker 不应过度使用,我们应该仅在必要时使用,否则会增加管理 Worker 的复杂性。

想要了解更多?欢迎在Twitter上与我们交流:)

你可以给我买杯咖啡来支持我 ☕

电子书

使用 ChatGPT 调试 ReactJS 问题:50 个基本技巧和示例

ReactJS 优化技术和开发资源

Twitter 实时关注者数量

Twitter 统计数据

更多博客

  1. 使用 Vite 代替 CRA 开发 React 应用
  2. 使用 Next.js、NextAuth 和 TailwindCSS 的 Twitter 关注者追踪器
  3. 不要优化你的 React 应用,而是使用 Preact
  4. 如何将 React 应用加载时间缩短 70%
  5. 使用 Next.js、Tailwind 和 Vercel 构建支持暗黑模式的投资组合
  6. React 中不再导入 ../../../
  7. 10 个 React 包,包含 1K 个 UI 组件
  8. 5 个软件包可在开发过程中优化和加速您的 React 应用
  9. 如何在 React 中以优化和可扩展的方式使用 Axios
  10. 15 个自定义 Hooks 让你的 React 组件更轻量
  11. 免费托管 React 应用的 10 种方法
  12. 如何在单页应用程序中保护 JWT
文章来源:https://dev.to/nilanth/multi-threaded-react-app-using-useworker-gf8
PREV
React 中不再导入 ../../../
NEXT
如何在 React 中以优化和可扩展的方式使用 Axios