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

如何在LightningChart JS React项目中实现单WebSocket连接下的多图表实时数据流分离

如何在LightningChart JS React项目中实现单WebSocket连接下的多图表实时数据流分离

这个场景我之前碰过好多次——一开始单图表用单WS连接没问题,图表多了之后不仅客户端资源吃紧,服务器那边的连接数压力也上来了,确实得改成单连接分发的模式。用React的上下文(Context)来统一管理WebSocket是最适合这种轻量场景的方案,不用引入复杂的状态管理库,代码也清晰。下面一步步给你讲怎么实现:


1. 先做一个全局的WebSocket上下文提供者

这个组件负责维护唯一的WebSocket连接,接收服务器的所有数据,然后把数据分发给订阅的图表组件。我们用useRef来存连接实例和订阅者列表,避免组件重渲染时重复创建:

import { createContext, useContext, useEffect, useRef, useState } from 'react';

// 定义上下文类型,提供订阅数据的方法
type WSContextType = {
  subscribe: (chartKey: string, callback: (data: any) => void) => () => void;
};

const WSContext = createContext<WSContextType | undefined>(undefined);

export function WebSocketProvider({ children }: { children: React.ReactNode }) {
  const wsRef = useRef<WebSocket | null>(null);
  // 用Map存储每个图表key对应的回调函数集合
  const subscribersRef = useRef<Map<string, Set<(data: any) => void>>>(new Map());

  useEffect(() => {
    // 建立单一WebSocket连接
    const ws = new WebSocket(wsUrl);
    wsRef.current = ws;

    // 处理服务器消息
    ws.onmessage = (event) => {
      const receivedData = JSON.parse(event.data);
      
      // 这里假设后端返回的数据结构包含chartKey字段,用来标识属于哪个图表
      // 如果后端返回的是批量多图表数据(比如数组),就遍历分发
      if (Array.isArray(receivedData)) {
        receivedData.forEach(item => {
          const targetCallbacks = subscribersRef.current.get(item.chartKey);
          targetCallbacks?.forEach(callback => callback(item.data));
        });
      } else {
        const targetCallbacks = subscribersRef.current.get(receivedData.chartKey);
        targetCallbacks?.forEach(callback => callback(receivedData.data));
      }
    };

    // 可选:处理连接错误和关闭
    ws.onerror = (err) => console.error('WS连接错误:', err);
    ws.onclose = () => console.log('WS连接已关闭');

    // 清理函数:组件卸载时关闭连接,清空订阅者
    return () => {
      ws.close();
      subscribersRef.current.clear();
    };
  }, []);

  // 订阅方法:每个图表组件调用这个方法订阅对应key的数据
  const subscribe = (chartKey: string, callback: (data: any) => void) => {
    // 如果该key还没有订阅集合,就新建一个
    if (!subscribersRef.current.has(chartKey)) {
      subscribersRef.current.set(chartKey, new Set());
    }
    subscribersRef.current.get(chartKey)!.add(callback);

    // 返回取消订阅的函数,供组件卸载时调用
    return () => {
      const callbacks = subscribersRef.current.get(chartKey);
      if (callbacks) {
        callbacks.delete(callback);
        // 如果该key没有订阅者了,就从Map中移除,节省内存
        if (callbacks.size === 0) {
          subscribersRef.current.delete(chartKey);
        }
      }
    };
  };

  return (
    <WSContext.Provider value={{ subscribe }}>
      {children}
    </WSContext.Provider>
  );
}

// 自定义Hook,方便组件获取上下文
export function useWS() {
  const context = useContext(WSContext);
  if (!context) {
    throw new Error('useWS必须在WebSocketProvider内部使用');
  }
  return context;
}

2. 改造你的Chart组件,让它订阅数据而不是自己开连接

现在每个Chart组件只需要负责创建图表、订阅对应key的数据,不用再管理WebSocket了,代码会简洁很多:

import { useId, useContext, useEffect, useRef } from 'react';
import { useWS } from './WebSocketProvider';
import { LCContext } from './your-lc-context-path'; // 你的LightningChart上下文

type ChartProps = {
  chartKey: string; // 每个图表的唯一标识,用来匹配数据
};

export function Chart({ chartKey }: ChartProps) {
  const id = useId();
  const lc = useContext(LCContext);
  const { subscribe } = useWS();
  const lineSeriesRef = useRef<any>(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;

    // 订阅当前chartKey的数据
    const unsubscribe = subscribe(chartKey, (data) => {
      lineSeries.appendJSON(data);
    });

    // 清理函数:销毁图表,取消订阅
    return () => {
      chart.dispose();
      unsubscribe();
    };
  }, [id, lc, chartKey, subscribe]);

  return <div id={id} className="chart"></div>;
}

3. 在根组件中使用WebSocketProvider,渲染多个图表

最后在App或者根组件里把所有Chart包在WebSocketProvider里,每个Chart传入唯一的chartKey即可:

import { WebSocketProvider } from './WebSocketProvider';
import { Chart } from './Chart';

function App() {
  return (
    <WebSocketProvider>
      <div className="dashboard-grid">
        <Chart chartKey="temperature" />
        <Chart chartKey="humidity" />
        <Chart chartKey="pressure" />
        <Chart chartKey="windSpeed" />
        {/* 最多可以加10个甚至更多图表 */}
      </div>
    </WebSocketProvider>
  );
}

一些关键注意事项

  1. 后端数据格式配合:确保后端返回的每条数据都带有chartKey(或者你自定义的标识字段),这样前端才能准确把数据分发给对应图表。如果后端返回的是全量批量数据,也可以在Provider里先按chartKey分组再分发。
  2. 内存泄漏防护:用useRef存订阅者集合,组件卸载时调用unsubscribe移除回调,同时销毁LightningChart实例,避免内存泄漏。
  3. 可选优化:重连逻辑:如果需要高可靠性,可以在Provider里添加WS重连逻辑(比如连接断开后隔几秒自动重试),这个逻辑只需要写一次,所有图表都能受益。
  4. 性能优势:单一WS连接相比10个连接,能大幅减少客户端的网络资源占用,服务器端也不用维护多个连接,整体延迟和带宽消耗都会降低。LightningChart JS本身对大数据流的处理效率很高,分发数据的额外开销几乎可以忽略。

备注:内容来源于stack exchange,提问作者codegecko66

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 18:03:04