webworker 工程运用

39 阅读3分钟

平时项目中,金融场景,会涉及很多对数据源的二次处理,同时会对大量数据源进行,相关算法计算,再去展示,有的时候,首屏加载后, FID 指标不行,也就是用户在数据处理阶段,由于js线程被阻塞主了,所以不能对用户交互做立刻的响应.

那么我来结合具体案例来说明,为什么webworker 可以解决类似的问题呢

无webworker 版

数据量级 1千万,我举一些极端例子,方便说明

测试用例为党首屏渲染后,立刻点击按钮,看数据变化是否有延迟,这个时候我本地测试至少有接近两秒延迟,状态才更新到视图上.

import React, { useEffect, useRef, useState } from "react";
import * as echarts from "echarts";
import { useMemo } from "react";
// 模拟大量原始数据
const generateRawData = (count) => {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    rawValue: Math.random() * 1000,
  }));
};
const LineChart = () => {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
  const [cont, setcont] = useState(1);
  const [dataSize, setDataSize] = useState(10000000); // 可调整数据量大小
  const [processedData, setProcessedData] = useState([]);
  // 生成测试数据
  useEffect(() => {
    console.time("生成数据");
    const data = generateRawData(dataSize);
    const processedData = data.map((item) => ({
      name: "点" + item.id,
      value: item.rawValue * 0.8 + Math.sin(item.id) * 50,
    }));
    console.timeEnd("生成数据");
    setProcessedData(processedData);
  }, [dataSize]);

  useEffect(() => {
    // 初始化图表
    if (chartRef.current) {
      // 销毁之前的实例(如果存在)
      if (chartInstance.current) {
        chartInstance.current.dispose();
      }
      chartInstance.current = echarts.init(chartRef.current);
    }

    // 定义图表配置项
    const option = {
      title: {
        text: "基础折线图示例",
        left: "center",
      },
      tooltip: {
        trigger: "axis",
      },
      legend: {
        data: ["销售额"],
        top: "10%",
      },
      xAxis: {
        type: "category",
        data: processedData.map((item) => item.name),
      },
      yAxis: {
        type: "value",
      },
      series: [
        {
          name: "销售额",
          type: "line",
          data: processedData.map((item) => item.value),
          // 可选:添加平滑曲线
          smooth: true,
          // 可选:添加区域填充
          areaStyle: {},
        },
      ],
    };

    // 设置图表配置项
    if (chartInstance.current) {
      chartInstance.current.setOption(option);
    }

    // 处理窗口大小变化
    const handleResize = () => {
      if (chartInstance.current) {
        chartInstance.current.resize();
      }
    };

    window.addEventListener("resize", handleResize);

    // 清理函数
    return () => {
      window.removeEventListener("resize", handleResize);
      if (chartInstance.current) {
        chartInstance.current.dispose();
        chartInstance.current = null;
      }
    };
  }, []);

  return (
    <div>
      <div
        // ref={chartRef}
        style={{ width: "600px", height: "400px", margin: "50px auto" }}
      />
      <div>{cont}</div>
      <button
        onClick={() => {
          setcont((v) => v + 1);
        }}
      >
        点我
      </button>
    </div>
  );
};

export default LineChart;

webworekr版本

这个时候差别在于,我把计算逻辑放在webworker 辅助线程里,也就是说在数据处理阶段,由子线程来处理,主线程并不阻塞,因此,当你点击按钮后,是可以立刻被响应的,实测延迟很小.

import React, { useEffect, useRef, useState } from "react";
import * as echarts from "echarts";
import { useMemo } from "react";
// 模拟大量原始数据
const generateRawData = (count) => {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    rawValue: Math.random() * 1000,
  }));
};

// === 版本二:使用 Web Worker ===
// dataProcessorWorker.js (Web Worker 脚本)
// 注意:在实际项目中,这应该是一个单独的文件
const workerCode = `
self.onmessage = function(e) {
  console.time('Web Worker Processing');
  const processedData = e.data.map(item => ({
    name: '点' + item.id,
    value: item.rawValue * 0.8 + Math.sin(item.id) * 50
  }));
  console.timeEnd('Web Worker Processing');
  self.postMessage(processedData);
};
`;

// 创建 Blob URL 作为 Worker
const createWorkerBlobUrl = () => {
  const blob = new Blob([workerCode], { type: "application/javascript" });
  return URL.createObjectURL(blob);
};

const LineChartWithWorker = () => {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
  const [rawData, setRawData] = useState([]);
  const [cont, setcont] = useState(1);
  const [processedData, setProcessedData] = useState([]);
  const [dataSize, setDataSize] = useState(10000000); // 可调整数据量大小

  // 生成测试数据
  useEffect(() => {
    console.time("生成数据");
    const data = generateRawData(dataSize);
    console.timeEnd("生成数据");
    setRawData(data);
  }, [dataSize]);

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

    console.log("发送数据到 Web Worker...");
    worker.postMessage(rawData);

    worker.onmessage = (e) => {
      console.log("e", e);
      console.log("收到 Web Worker 处理结果");
      setProcessedData(e.data);
    };

    worker.onerror = (error) => {
      console.error("Web Worker 错误:", error);
    };

    return () => {
      worker.terminate();
      URL.revokeObjectURL(workerUrl);
    };
  }, [rawData]);
  useEffect(() => {
    console.log("processedData", processedData);
    // 初始化图表
    if (chartRef.current) {
      // 销毁之前的实例(如果存在)
      if (chartInstance.current) {
        chartInstance.current.dispose();
      }
      chartInstance.current = echarts.init(chartRef.current);
    }

    // 定义图表配置项
    const option = {
      title: {
        text: "基础折线图示例",
        left: "center",
      },
      tooltip: {
        trigger: "axis",
      },
      legend: {
        data: ["销售额"],
        top: "10%",
      },
      xAxis: {
        type: "category",
        data: processedData.map((item) => item.name),
      },
      yAxis: {
        type: "value",
      },
      series: [
        {
          name: "销售额",
          type: "line",
          data: processedData.map((item) => item.value),
          // 可选:添加平滑曲线
          smooth: true,
          // 可选:添加区域填充
          areaStyle: {},
        },
      ],
    };

    // 设置图表配置项
    if (chartInstance.current) {
      chartInstance.current.setOption(option);
    }

    // 处理窗口大小变化
    const handleResize = () => {
      if (chartInstance.current) {
        chartInstance.current.resize();
      }
    };

    window.addEventListener("resize", handleResize);

    // 清理函数
    return () => {
      window.removeEventListener("resize", handleResize);
      if (chartInstance.current) {
        chartInstance.current.dispose();
        chartInstance.current = null;
      }
    };
  }, [processedData]);

  return (
    <div>
      <div
        // ref={chartRef}
        style={{ width: "600px", height: "400px", margin: "50px auto" }}
      />
      <div>{cont}</div>
      <button
        onClick={() => {
          setcont((v) => v + 1);
        }}
      >
        点我
      </button>
    </div>
  );
};

export default LineChartWithWorker;


来看点直观的对比

image.png

我不用webwoker 的时候,我发现我点击后,只有等待processdata设置完毕后,2才出现

但是当我用webworker ,会发现首屏渲染时,我立刻点击按钮,2很快会出现,并没有阻塞到主线程的任务,因此其实webworker 并不是说要去解决首屏渲染时长的问题,而是解决,因为主线程任务,较长,导致其他任务无法立刻响应的问题 但是弊端在于,由于是子线程,无法直接使用window,dom操作,等,为了避免竞态等问题.

image.png