使用Socket.io更新MUI DataGrid时仅单条数据新增且每次更新被覆盖
核心问题原因
- 闭包捕获旧状态:Websocket的
add_order事件绑定的是组件某次渲染时生成的回调函数,该函数内部引用的data值是绑定时刻的快照,不会随组件重新渲染更新。后续收到websocket消息执行setData时,都是基于绑定时刻的旧data对象做展开拼接,不会合并之前已经新增的socket数据,最终表现为每次新增的socket数据直接覆盖上一次的新增项。 - 无连接清理逻辑:合并了API加载、socket初始化逻辑的
useEffect依赖列表包含data和socketActive,每次data变化就会重新执行整个effect,新建websocket连接但不关闭旧连接,导致重复绑定事件、多个旧连接同时触发回调,进一步放大状态更新异常。 - 冗余状态同步:表格组件内部把props传入的
tableData又复制了一份存在本地state中,多了一层不必要的同步链路,容易出现更新不一致。
修复步骤
- 所有依赖最新状态的
setData操作,改用函数式更新写法,回调内部接收React提供的最新前值prevData做合并,彻底解决闭包捕获旧状态的问题。 - 拆分独立的逻辑effect:初始API加载逻辑单独放在只执行一次的effect中,socket连接逻辑单独放在加载完成后触发一次的effect中,同时加连接清理逻辑,避免状态变化时反复重建socket。
- 删除表格组件中冗余的本地state,直接透传父组件传入的props给DataGrid,去掉不必要的状态同步链路。
修复后核心代码
父组件逻辑
const [data, setData] = useState({}); const [socketActive, setSocketActive] = useState(false); const [isInitialLoaded, setIsInitialLoaded] = useState(false); // 初始数据加载 const handleResponse = (response) => { const result = response.reduce((acc, prop) => { acc[prop.order_id] = { id: prop.order_id, orderId: prop.order_id, price: prop.price.toString(), volume: prop.max_amount_currency_to_trade, minVolume: prop.min_amount_currency_to_trade, buyVolume: prop.max_volume_currency_to_pay + " €", minBuyVolume: prop.min_volume_currency_to_pay + " €", user: prop.trading_partner_information.username, }; return acc; }, {}); setData(result); }; // 仅在组件挂载时加载一次初始API数据 useEffect(() => { axios .get(process.env.REACT_APP_BITCOIN_SERVICE + "/order/book?type=buy") .then((response) => { handleResponse(response.data.orders); setIsInitialLoaded(true); }) .catch((exception) => console.error(exception)); }, []); // 初始数据加载完成后,建立一次websocket连接 useEffect(() => { if (!isInitialLoaded) return; const websocket = io("https://ws-mig.bitcoin.de:443/market", { path: "/socket.io/1", }); websocket.on("connect", () => setSocketActive(true)); websocket.on("disconnect", () => setSocketActive(false)); websocket.on("add_order", (order) => { // 订单过滤逻辑 if ( order.trading_pair === "btceur" && order.order_type === "buy" && (order.payment_option === "1" || order.payment_option === "3") ) { // 用函数式更新获取最新的data值 setData((prevData) => { // 同ID订单已存在则不重复添加 if (prevData[order.order_id]) return prevData; return { ...prevData, [order.order_id]: { id: order.order_id, orderId: order.order_id, price: order.price.toString(), volume: order.amount, minVolume: order.min_amount, buyVolume: Number(order.volume).toFixed(2) + " €", minBuyVolume: Number(order.min_amount * order.price).toFixed(2) + " €", user: "not set through socket", }, }; }); } }); websocket.on("remove_order", (order) => { setData((prevData) => { const newData = {...prevData}; delete newData[order.order_id]; return newData; }); }); // 组件卸载时断开连接 return () => { websocket.disconnect(); }; }, [isInitialLoaded]);
表格组件逻辑
// 删除冗余的本地state,直接透传props数据 return (<div key="tradingTableGrid" style={{ height: 450, width: "100%" }}> <DataGrid rows={Object.values(props.tableData)} columns={tableHead} rowHeight={30} pageSize={10} onRowClick={(row) => openModal(row)} /> </div>);
内容的提问来源于stack exchange,提问作者Matus von Matushausen
相关产品推荐
相关产品推荐

