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

React严格模式下WebSocket更新组件时prevNodes异常问题求助

问题:React严格模式下WebSocket连接切换后setNodes的prevNodes保留旧系统节点

我正在开发一个使用WebSocket实现实时通信的React组件,WebSocket连接在依赖system.id的useEffect钩子中建立,system.id预计很少变化。组件简化代码如下:

export function Playground({ system }) {
  const [nodes, setNodes] = useState(system.nodes);
  const [edges, setEdges] = useState(system.edges);

  useEffect(() => {
    const ws = new WebSocket(`ws://localhost:8080/ws/${system.id}`);

    ws.onopen = () => {
      console.log('WebSocket connection opened:', system.id);
    }

    ws.onmessage = (event) => {
      const messageData = JSON.parse(event.data);
      console.log('WebSocket message received:', messageData);

      setNodes((prevNodes) => {
        let newNodes = prevNodes.map(node => {
          if (node.id === messageData.nodeId) {
            return {
              ...node,
              data: {
                ...node.data,
                metrics: messageData.metrics,
              }
            };
          } else {
            return node;
          }
        });

        return newNodes;
      });
    }

    return () => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.close();
      }
    }
  }, [system.id]);
}

但在React严格模式下运行时遇到问题:Playground组件多次渲染,控制台打印两次“WebSocket connection opened: systemID1”,之后打印一次“WebSocket connection opened: systemID2”。当收到WebSocket消息时,setNodes更新函数中的prevNodes变量仍保留systemID1的节点,即使已建立systemID2的连接,导致节点更新失败。关闭严格模式则无此问题。

请问为何prevNodes会保留旧系统的节点?我遗漏了什么?如何确保prevNodes反映当前系统ID的节点?


原因分析

  1. 严格模式的双重执行与闭包陷阱
    React严格模式在开发环境中会重复执行组件渲染、useEffect等生命周期逻辑,用于检测潜在的代码问题。第一次执行useEffect后会立即调用清理函数关闭WebSocket,第二次执行会重新建立连接。当system.id从systemID1切换到systemID2时,旧的(systemID1的第二次useEffect创建的)WebSocket连接的onmessage回调,由于闭包特性依然绑定着该useEffect执行时的状态环境——此时组件的nodes状态还未更新为新系统的节点数据,若旧连接在关闭前仍收到消息,就会导致setNodes使用旧的prevNodes进行更新。

  2. 未同步状态与system的变化
    组件初始化时useState(system.nodes)仅设置了nodes的初始值,当system整体变化(包括system.id变更时system.nodes也更新),没有任何逻辑同步更新nodes和edges状态,导致nodes始终停留在第一个system的节点数据上,即便新的WebSocket连接建立,setNodes的prevNodes自然还是旧数据。

解决方案

1. 同步状态与system的变化

添加一个专门监听system变化的useEffect,在system更新时同步刷新nodes和edges状态:

useEffect(() => {
  setNodes(system.nodes);
  setEdges(system.edges);
}, [system]);

2. 完善WebSocket的清理逻辑

在useEffect的清理函数中,不仅关闭WebSocket,还要先移除onopen和onmessage回调,彻底避免旧回调在连接关闭前继续执行:

return () => {
  // 先移除回调,切断闭包引用
  ws.onopen = null;
  ws.onmessage = null;
  // 处理CONNECTING状态的连接,避免遗漏关闭
  if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
    ws.close();
  }
};

3. (可选)验证消息归属的系统

如果WebSocket消息中包含systemId字段,可以在onmessage回调中先验证消息是否属于当前连接的系统,避免错误更新旧系统的节点:

ws.onmessage = (event) => {
  const messageData = JSON.parse(event.data);
  // 仅处理当前系统的消息
  if (messageData.systemId !== system.id) return;
  
  setNodes((prevNodes) => {
    let newNodes = prevNodes.map(node => {
      if (node.id === messageData.nodeId) {
        return {
          ...node,
          data: {
            ...node.data,
            metrics: messageData.metrics,
          }
        };
      } else {
        return node;
      }
    });
    return newNodes;
  });
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 02:15:54