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

React+Socket.io实时聊天:useEffect重复渲染致消息重复接收

React + Socket.io 实时聊天应用:重复消息问题与useEffect疑问解答

问题背景

基于React和Socket.io开发实时聊天应用时,出现其他用户收到两条相同消息的问题,接收端控制台会重复打印两次received message。同时对useEffect的使用存在两个核心疑问:

  • 为何socket.on要放在useEffect中?
  • 既然useEffect仅在特定时机执行,为何能持续接收socket消息?

核心代码片段

路由配置

function App() {
  return (
    <div className="App">
      <Router>
        <Routes>
          <Route path="/" element={<Join />} />
          <Route path="/chat/:name" element={<Chat />} />
        </Routes>
      </Router>
    </div>
  );
}

聊天页面核心逻辑

function Chat () {
  const [messages, addMessage] = useState([{
    username: "chatbot",
    time: new Date(),
    message: "Welcome to the chatroom 😈"
  }]);

  const location = useLocation();
  const [currentUser, updateUser] = useState(() => {
    const user = location.pathname.split('/chat/')[1];
    socket.emit("join_chat", {
      username: user
    })
    return user;
  });

  useEffect(() => {
    socket.on("receive_message", (data) => {
      console.log('received message');
      addMessage(prevMessages => {
        return [...prevMessages, {
          username: data.username,
          time: new Date(),
          message: data.message
        }]
      })
    })
  }, [socket])

  const sendMessage = (e) => {
    e.preventDefault();
    socket.emit("send_message", {
      message: currentMessage,
      username: currentUser,
    });
  }
}

问题分析与解决

1. 重复消息的原因与修复

重复消息的本质是socket事件监听器被重复绑定:你的useEffect只负责绑定receive_message事件,但没有在组件卸载或依赖变化时移除旧的监听器。当组件因状态更新(比如messages变化)重渲染时,新的监听器会叠加在旧的之上,导致一条消息触发多次回调。

修复方案:给useEffect添加清理函数

useEffect(() => {
  // 单独定义回调函数,方便后续移除
  const handleReceiveMessage = (data) => {
    console.log('received message');
    addMessage(prevMessages => {
      return [...prevMessages, {
        username: data.username,
        time: new Date(),
        message: data.message
      }]
    })
  };

  // 绑定事件监听器
  socket.on("receive_message", handleReceiveMessage);

  // 清理函数:组件卸载或依赖变化时移除监听器
  return () => {
    socket.off("receive_message", handleReceiveMessage);
  };
}, [socket])

额外注意:确保你的socket是全局单例实例,如果每次进入Chat组件都创建新的socket连接,也会导致重复监听问题,建议在应用初始化时只创建一次socket实例。

2. 关于useEffect的疑问解答

为什么socket.on要放在useEffect中?

React组件会因状态更新、props变化等原因多次重渲染,如果直接在组件顶层写socket.on,每次重渲染都会重新绑定一次事件,最终导致大量重复监听器,引发内存泄漏和逻辑混乱。

useEffect的核心作用是控制代码的执行时机:

  • 依赖数组为空时,只在组件挂载时执行一次
  • 依赖变化时,会先执行上一次的清理函数,再执行新的回调
  • 配合清理函数可以避免重复绑定、内存泄漏等问题

为什么useEffect只执行一次,却能持续接收socket消息?

socket.on是注册事件监听器的操作——一旦绑定成功,只要socket连接保持活跃,后续所有receive_message事件触发时,回调函数都会被执行。useEffect只是负责在合适的时机(比如组件挂载)把监听器绑定到socket上,绑定完成后,监听器就独立于useEffect存在,直到被socket.off移除。

简单说:useEffect是"安装"监听器的工具,安装完成后,监听器就会一直工作,不需要useEffect持续运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 11:50:23