You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 10:32:29