如何在React中基于LightningChart JS实现单WebSocket连接下多图表的实时数据流分发?
React + LightningChart JS 多图表单WebSocket数据流分发最佳实践
问题拆解
哥们,这个场景我太熟了——不少做实时仪表盘的开发者都踩过类似的坑。先戳破你原来方案的核心痛点:
- 10个图表各自建WebSocket连接:既浪费服务器资源,浏览器对同域名的并发连接数也有限制,很容易出现连接阻塞的问题
- 单连接但在每个Chart组件里过滤数据:每次收到消息都要重复解析JSON,还要遍历判断是否属于当前图表,数据量上来后性能损耗会特别明显
解决方案:全局WebSocket订阅分发机制
核心思路是在顶层组件创建唯一的WebSocket连接,通过订阅/发布模式把数据精准分发给对应的图表组件,完全避免重复解析和无效处理,这也是兼顾性能的最优方案。
1. 封装WebSocket全局Provider
先写一个WebSocket的Context Provider,负责管理唯一连接和订阅逻辑,把订阅方法暴露给子组件:
// WebSocketContext.tsx import { createContext, useContext, useEffect, useState, useCallback } from 'react'; // 定义上下文类型:提供订阅指定指标的方法 type WebSocketContextType = { subscribe: (metricKey: string, callback: (data: any) => void) => () => void; }; const WebSocketContext = createContext<WebSocketContextType | undefined>(undefined); export const WebSocketProvider = ({ children }: { children: React.ReactNode }) => { // 用Map存储每个指标对应的回调集合 const [subscribers, setSubscribers] = useState<Map<string, Set<(data: any) => void>>>(new Map()); useEffect(() => { // 初始化唯一的WebSocket连接 const ws = new WebSocket(wsUrl); ws.onmessage = (event) => { // 假设后端返回的格式是 { metric: "temperature", data: [...] } // 这里只解析一次JSON,然后直接分发给订阅该指标的图表 const payload = JSON.parse(event.data); const metricCallbacks = subscribers.get(payload.metric); if (metricCallbacks) { metricCallbacks.forEach(callback => callback(payload.data)); } }; // 组件卸载时关闭连接 return () => ws.close(); }, [subscribers]); // 封装订阅方法,返回取消订阅的函数 const subscribe = useCallback((metricKey: string, callback: (data: any) => void) => { setSubscribers(prev => { const newSubscribers = new Map(prev); const callbacks = newSubscribers.get(metricKey) || new Set(); callbacks.add(callback); newSubscribers.set(metricKey, callbacks); return newSubscribers; }); return () => { setSubscribers(prev => { const newSubscribers = new Map(prev); const callbacks = newSubscribers.get(metricKey); if (callbacks) { callbacks.delete(callback); // 如果该指标没有订阅者了,从Map里删掉,减少内存占用 if (callbacks.size === 0) newSubscribers.delete(metricKey); } return newSubscribers; }); }; }, []); return ( <WebSocketContext.Provider value={{ subscribe }}> {children} </WebSocketContext.Provider> ); }; // 自定义Hook,方便子组件使用 export const useWebSocket = () => { const context = useContext(WebSocketContext); if (!context) throw new Error('useWebSocket必须在WebSocketProvider内部使用'); return context; };
2. 修改Chart组件,通过订阅获取数据
把原来的Chart组件改成通过useWebSocket订阅指定指标的数据,不再自己创建WebSocket:
// Chart.tsx import { useId, useEffect, useContext, useRef } from 'react'; import { LCContext } from './LCContext'; import { useWebSocket } from './WebSocketContext'; // 给Chart组件加一个props:指定当前图表对应的指标键 type ChartProps = { metricKey: string; }; export function Chart({ metricKey }: ChartProps) { const id = useId(); const lc = useContext(LCContext); const { subscribe } = useWebSocket(); // 用useRef保存lineSeries,避免每次渲染重新创建 const lineSeriesRef = useRef<ReturnType<typeof lc.ChartXY['addLineSeries']> | null>(null); useEffect(() => { const container = document.getElementById(id) as HTMLDivElement; if (!container || !lc) return; // 初始化LightningChart图表 const chart = lc.ChartXY({ container }); const lineSeries = chart .addLineSeries({ schema: { x: { pattern: "progressive" }, y: { pattern: null }, }, }) .setMaxSampleCount(100_000); lineSeriesRef.current = lineSeries; // 订阅当前指标的数据,收到后直接更新图表 const unsubscribe = subscribe(metricKey, (data) => { lineSeriesRef.current?.appendJSON(data); }); // 组件卸载时清理图表和取消订阅 return () => { chart.dispose(); unsubscribe(); }; }, [id, lc, metricKey, subscribe]); return <div id={id} className="chart"></div>; }
3. 仪表盘组件:批量渲染多个图表
最后在你的仪表盘组件里,用WebSocketProvider包裹所有Chart组件,传入不同的metricKey即可:
// Dashboard.tsx import { WebSocketProvider } from './WebSocketContext'; import { Chart } from './Chart'; export function Dashboard() { // 示例:10个不同的测量指标 const metrics = [ "temperature", "pressure", "humidity", "voltage", "current", "rpm", "fuelLevel", "oilPressure", "coolantTemp", "airFlow" ]; return ( <WebSocketProvider> <div className="dashboard-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '1rem' }}> {metrics.map(metric => ( <Chart key={metric} metricKey={metric} /> ))} </div> </WebSocketProvider> ); }
额外性能优化建议
- 后端数据格式优化:确保后端返回的消息已经按metric分类,避免前端做额外的过滤工作
- LightningChart配置优化:根据实际数据频率调整
setMaxSampleCount,避免内存占用过高;如果数据频率极高,可以考虑批量append数据(比如攒5-10条再更新图表),减少重绘次数 - 避免不必要的重渲染:用
useRef保存图表实例和回调,确保只有必要的依赖变化时才重新执行effect - 错误处理:可以给WebSocket加
onerror和onclose回调,实现重连逻辑,保证实时数据流的稳定性
内容的提问来源于stack exchange,提问作者codegecko66
相关产品推荐
相关产品推荐

