平时项目中,金融场景,会涉及很多对数据源的二次处理,同时会对大量数据源进行,相关算法计算,再去展示,有的时候,首屏加载后, 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;
来看点直观的对比
我不用webwoker 的时候,我发现我点击后,只有等待processdata设置完毕后,2才出现
但是当我用webworker ,会发现首屏渲染时,我立刻点击按钮,2很快会出现,并没有阻塞到主线程的任务,因此其实webworker 并不是说要去解决首屏渲染时长的问题,而是解决,因为主线程任务,较长,导致其他任务无法立刻响应的问题 但是弊端在于,由于是子线程,无法直接使用window,dom操作,等,为了避免竞态等问题.