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

