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

SignalR WebSocket数据存入数组及Leaflet轨迹移动问题排查

问题

我有一个连接SignalR WebSocket的异步函数,希望遍历接收的每个ECSEquipmentLocationUpdate对象并将其存入数组,之后使用该数组中的X、Y值在Leaflet画布中实现物体的轨迹移动(数组首个元素为起始位置,最后一个元素为最新位置)。但目前数组无法正常存入数据,且尝试直接存入过滤后的对象时,数据可能不按时间顺序排列,请求解决建议。

相关代码

SignalR数据处理代码

interface ECSEquipmentLocationUpdate {
  X: number;
  Y: number;
  Phi: number;
  EquipmentId: string;
  EquipmentType: EquipmentType;
}

let ascData: Record<string, ECSEquipmentLocationUpdate> = {};
let filteredASC = {};
let ascDataList: any[] = []

export const receiveASCData = async () => {
  connection.on('ReceiveEquipmentLocation', EquipmentLocation => {
    equipmentLocation = JSON.parse(EquipmentLocation)
    ascData[equipmentLocation.EquipmentType] = equipmentLocation
    filteredASC = ascData[1]
    console.log(filteredASC, 'filtered asc Objects')
    for (const filteredASCItem of filteredASC) {
      ascDataList.push(filteredASCItem)
    }
    console.log('ascDataList' , ascDataList)
    return ascDataList
  })
}

Leaflet组件代码

import L from "leaflet";
import React, { useEffect, useState } from "react";
import { Popup } from "react-leaflet";
import { LeafletTrackingMarker } from "react-leaflet-tracking-marker";
import ascIcon from "../../../../../assets/che-items/ASCIcon.svg";
import { ASCTabsWrapper } from "../asc-items/ASCTabsWrapper";
import { receiveASCData } from "../../../../../services/equipment/EquipmentServices";

const icon = L.icon({
  iconSize: [150, 100],
  popupAnchor: [2, -20],
  iconUrl: ascIcon,
});

export default function ASC01({ data }: any) {

  const { lat, lng } = data;
  const [prevPos, setPrevPos] = useState([lat, lng]);
  const [ascId, setASCID] = useState(null)
  const [ascType, setAscYPE] = useState(null)
  const [ascXvalue, setAscXValue] = useState(null)
  const [ascYvalue, setAscYValue] = useState(null)
  const [ascObject, setAscObject] = useState({})

  const ascItems: any[] = [];

  useEffect(() => {
    if (prevPos[1] !== lng && prevPos[0] !== lat) {
      setPrevPos([lat, lng]);
    }
  }, [lat, lng, prevPos]);

  useEffect(() => {
    receiveASCData().then((ascObject) => setAscObject(ascObject))
    console.log('from asc01', ascObject)
  }, [])

  // if (!ascObject) return <div>Loading</div>


  return (
    <LeafletTrackingMarker
      icon={icon}
      position={[lat, lng]}
      previousPosition={prevPos}
      duration={4000}
    >
      <Popup>
        <ASCTabsWrapper />
        {ascItems} 
      </Popup>
    </LeafletTrackingMarker>
  );
}

问题分析
  1. 数据结构错误:ascData是键值对对象,filteredASC = ascData[1]拿到的是单个ECSEquipmentLocationUpdate对象,用for...of遍历单个对象会遍历其属性名,导致ascDataList存入的是字符串而非完整对象。
  2. 异步逻辑失效:receiveASCData是异步函数,但connection.on是事件监听,回调里的return ascDataList无法被外部then捕获,组件无法拿到更新后的数据。
  3. 时序无保障:SignalR推送的消息可能因网络延迟乱序,现有代码没有处理排序逻辑。
  4. 状态管理不当:全局数组ascDataList无法触发React组件的重新渲染,组件无法响应数据更新。

解决方案

一、修正SignalR数据处理逻辑

调整数据存储方式,新增时间戳保障顺序,通过回调通知组件更新:

interface ECSEquipmentLocationUpdate {
  X: number;
  Y: number;
  Phi: number;
  EquipmentId: string;
  EquipmentType: EquipmentType;
  Timestamp: number; // 要求后端推送时带上时间戳
}

// 直接存储符合条件的轨迹数据
let ascDataList: ECSEquipmentLocationUpdate[] = [];
// 用于通知组件的回调函数
type UpdateCallback = (data: ECSEquipmentLocationUpdate[]) => void;
let updateCallback: UpdateCallback | null = null;

export const receiveASCData = (callback: UpdateCallback) => {
  updateCallback = callback;
  
  connection.on('ReceiveEquipmentLocation', (rawData) => {
    const location = JSON.parse(rawData) as ECSEquipmentLocationUpdate;
    
    // 只过滤EquipmentType为1的设备
    if (location.EquipmentType === 1) {
      ascDataList.push(location);
      // 按时间戳排序,保证数据时序正确
      ascDataList.sort((a, b) => a.Timestamp - b.Timestamp);
      // 通知组件更新
      updateCallback?([...ascDataList]);
    }
  });
};

// 组件卸载时清理监听,避免内存泄漏
export const stopReceivingASCData = () => {
  connection.off('ReceiveEquipmentLocation');
  updateCallback = null;
};

二、修正React组件状态管理

用React状态存储轨迹数据,响应SignalR的更新:

import L from "leaflet";
import React, { useEffect, useState } from "react";
import { Popup } from "react-leaflet";
import { LeafletTrackingMarker } from "react-leaflet-tracking-marker";
import ascIcon from "../../../../../assets/che-items/ASCIcon.svg";
import { ASCTabsWrapper } from "../asc-items/ASCTabsWrapper";
import { receiveASCData, stopReceivingASCData } from "../../../../../services/equipment/EquipmentServices";

// 导入类型定义
interface ECSEquipmentLocationUpdate {
  X: number;
  Y: number;
  Timestamp: number;
}

const icon = L.icon({
  iconSize: [150, 100],
  popupAnchor: [2, -20],
  iconUrl: ascIcon,
});

export default function ASC01({ data }: any) {
  const [trackData, setTrackData] = useState<ECSEquipmentLocationUpdate[]>([]);
  const [prevPos, setPrevPos] = useState([data.lat, data.lng]);
  const [currentPos, setCurrentPos] = useState([data.lat, data.lng]);

  useEffect(() => {
    // 注册数据更新回调
    receiveASCData((updatedList) => {
      setTrackData(updatedList);
      
      if (updatedList.length > 0) {
        const lastIdx = updatedList.length - 1;
        // 注意:确认X/Y是否对应经纬度的lng/lat,需根据实际坐标系统调整
        const newCurrent = [updatedList[lastIdx].Y, updatedList[lastIdx].X];
        const newPrev = lastIdx > 0 ? [updatedList[lastIdx-1].Y, updatedList[lastIdx-1].X] : prevPos;
        
        setPrevPos(newPrev);
        setCurrentPos(newCurrent);
      }
    });

    // 组件卸载时清理监听
    return () => stopReceivingASCData();
  }, []);

  return (
    <LeafletTrackingMarker
      icon={icon}
      position={currentPos}
      previousPosition={prevPos}
      duration={4000}
    >
      <Popup>
        <ASCTabsWrapper />
        <div>轨迹点数量:{trackData.length}</div>
      </Popup>
    </LeafletTrackingMarker>
  );
}

额外注意事项
  • 坐标转换:确认X/Y是否需要转换为Leaflet的[纬度, 经度]格式,若为平面坐标需额外做映射处理。
  • 内存优化:若轨迹数据量过大,可限制数组长度(如只保留最近100个点),避免内存占用过高。
  • 连接容错:添加SignalR连接状态监听,处理断连重连场景,避免数据丢失。

内容的提问来源于stack exchange,提问作者Farid Guzman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:25:55