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

React中useEffect触发Socket IO事件两次问题求助

解决Socket IO事件在useEffect中触发两次的问题

问题根源分析

你的代码里有两个核心问题导致事件重复触发:

  1. useEffect依赖数组冗余:依赖里包含了players和setPlayer,players每次更新都会触发useEffect重新执行,重新创建Socket连接并绑定事件,旧连接没断开的话就会存在两个活跃连接,同一事件被两次监听。
  2. Socket连接未彻底清理:之前的清理函数只移除了事件监听,但没有断开旧的Socket连接,旧连接依然会接收服务端广播的事件。

分步解决方案

1. 修正useEffect的依赖数组与清理逻辑

setPlayers是React useState生成的稳定函数,不需要加入依赖;players绝对不能放在依赖里,否则每次玩家列表更新都会重建连接。只保留gameID作为依赖(仅当游戏ID变化时才需要重新连接),同时在清理函数中断开旧连接:

const socket = useRef();

useEffect(() => {
    // 创建Socket连接
    socket.current = io(SOCKET_URL, {
      query: { gameID: gameID },
    });

    // 抽离事件处理函数,方便后续精准移除监听
    const handlePlayerJoin = (player) => {
      console.log("new player:", player);
      setPlayers(prevPlayers => [...prevPlayers, player]);
    };

    const handlePlayerLeave = (player) => {
      console.log("user left:", player);
      setPlayers(prevPlayers => prevPlayers.filter(p => p.username !== player.username));
    };

    socket.current.on("join-game", handlePlayerJoin);
    socket.current.on("leave-game", handlePlayerLeave);

    // 清理函数:断开连接+移除监听
    return () => {
      socket.current.off("join-game", handlePlayerJoin);
      socket.current.off("leave-game", handlePlayerLeave);
      socket.current.disconnect(); // 彻底断开旧连接,避免残留
    };
}, [gameID]); // 仅保留gameID作为依赖项

2. 移除客户端joiningGame中的重复setPlayers

你在joiningGame里既调用了socket.emit("join-game"),又手动执行setPlayers,而服务端收到后会广播该事件,客户端监听后又会执行一次setPlayers,导致玩家被重复添加。删掉手动的setPlayers:

const joiningGame = async (user) => {
    await fetchGame();
    const player = new Player(user.username, game.current.startingHealth, 0, 0);
    setPlayer(player);
    const { data } = await joinGame({ gameID: gameID, player: player });
    if (data) {
      socket.current.emit("join-game", player);
      // 移除下面这句,交给Socket事件统一处理
      // setPlayers((players) => [...players, player]);
    }
};

3. 服务端确认广播目标准确性

确保服务端使用当前连接对应的游戏ID进行广播,避免错误广播到其他房间:

socket.on("join-game", (player) => {
    console.log(player, 'is joining');
    // 从连接的query中获取当前游戏ID,确保广播目标正确
    const targetGameID = socket.handshake.query.gameID;
    io.to(targetGameID).emit("join-game", player);
});

额外验证点

  • 打开浏览器控制台的Network标签,查看WebSocket连接数,确认每次切换gameID时旧连接会被断开,仅存在一个活跃连接
  • 检查组件是否存在重复渲染的情况(比如被多次挂载),避免重复初始化Socket

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:36:18