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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 14:06:05