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

React中Socket.io因页面重渲染异常断开重连问题求助

问题:Socket.io频繁重连且重渲染后无法恢复连接

我用Socket.io实现在线好友展示功能时遇到异常:每次页面触发重渲染(如修改个人信息、发送好友请求),负责初始化Socket的useEffect就会执行断开并重新连接操作。正常逻辑应该是仅在页面刷新或用户登出时断开Socket,日常操作不该触发此类行为。目前不仅频繁重连创建Socket实例不符合预期,更严重的是重渲染后Socket断开后无法重新连接。


客户端socket.js

import { io } from 'socket.io-client';

export const initializeSocket = (token, userInfoQuery, setOnlineFriends, setSocket) => {
  if (token && userInfoQuery) {
    const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });

    const friendIds = userInfoQuery.friends
    socket.emit("userFriends", friendIds);

    socket.on("onlineUsers", (users) => {
      setOnlineFriends(users);
    });

    socket.on("addFriend", (friend) => {
      console.log("Received friend request:", friend);
    });

    socket.on("removeFriend", (friend) => {
      console.log("Received friend request:", friend);
    });

    setSocket(socket);
    return socket;
  }
  return null;
};

客户端App.js

export default function App() {
  const { token, logout, login } = useContext(AuthContext);
  const [onlineFriends, setOnlineFriends] = useState([]);
  const [socket, setSocket] = useState(null);

  const userInfoQuery = useQuery("userInfo", async () => {
    const response = await axios.get(
      `${process.env.REACT_APP_API_URL}/api/user`,
      {
        headers: {
          Authorization: "Bearer " + token,
        },
      }
    );
    return response.data;
  });
  const userBooksQuery = useQuery("userBooks", async () => {
    const response = await axios.get(
      `${process.env.REACT_APP_API_URL}/api/user/books`,
      {
        headers: {
          Authorization: "Bearer " + token,
        },
      }
    );
    return response.data;
  });

  useEffect(() => {
    if (socket) {
      socket.connect();
    }
    if (token && userInfoQuery.data && !socket) {
      initializeSocket(token, userInfoQuery.data, setOnlineFriends, setSocket);
    }

    return () => {
      if (socket) {
        socket.disconnect();
      }
    };
  }, [token, userInfoQuery.data, socket]);

return ( ...
)

说明:useEffect依赖中必须加入userInfoQuery.data,否则在线好友数据无法通过Socket更新页面,但这会导致重渲染时触发useEffect清理函数断开Socket。尝试过在useEffect中调用socket.connect()重连无效;修改个人信息时通过queryClient.refetchQueries("userInfo")更新页面,会直接触发Socket断开问题。


服务端socketController.js

const knexConfig = require("../knexfile");
const { knex } = require("knex");
const db = knex(knexConfig);
const io = require("socket.io");
const jwt = require("jsonwebtoken");
require("dotenv").config();

module.exports.socketController = (io) => {
  const onlineFriends = new Map();

  io.on("connection", async (socket) => {
    console.log(`A user connected ${socket.id}`);
    try {
      const token = socket.handshake.auth.token;
      const decoded = jwt.verify(token, process.env.JWT_SIGN_KEY);
      const userId = decoded.user_id;

      await db("user").where("user_id", userId).update({ is_online: 1 });
      const user = await db("user").where("user_id", userId).first();

      onlineFriends.set(userId, user);

      socket.join(userId);

      const onlineFriendList = Array.from(onlineFriends.values());
      socket.emit("onlineUsers", onlineFriendList);

      socket.on("userFriends", async (friendsList) => {
        friendsList.forEach((friend) => {
          socket.join(friend.friend);
        });

        const onlineUsers = await db("user")
          .whereIn(
            "user_id",
            friendsList.map((friend) => friend.friend)
          )
          .andWhere("is_online", 1);

        onlineUsers.forEach((user) => onlineFriends.set(user.user_id, user));
        const updatedOnlineFriendList = Array.from(onlineFriends.values());

        socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);
      });
      socket.on("addFriend", async (friendId) => {
        const recipientUser = onlineFriends.get(friendId); 
        if (recipientUser) {
          const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id); 
          if (recipientSocket) {
            recipientSocket.emit("addFriend", userId);
          }
        }
      });
      socket.on("removeFriend", async (friendId) => {
        const recipientUser = onlineFriends.get(friendId);
        if (recipientUser) {
          const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id); 
          if (recipientSocket) {
            recipientSocket.emit("removeFriend", userId);
          }
        }
      });
      socket.on("disconnect", async () => {
        await db("user").where("user_id", userId).update({ is_online: 0 });

        onlineFriends.delete(userId);
        const updatedOnlineFriendList = Array.from(onlineFriends.values());

        socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);

        console.log(`${socket.id} disconnected`);
      });

    } catch (error) {
      console.error("Authentication error:", error.message);
      socket.disconnect();
    }
  });
};

补充:用户对象结构

{
    "avatar_image": "https://i.pravatar.cc/150?img=13",
    "email": "ross001@gmail.com",
    "favorite_genre": "comedy",
    "first_name": "ross",
    "friends": [{}],
    "goal_set": 9,
    "last_name": "test",
    "username": "rosss001"
}

解决方案

1. 拆分Socket初始化与好友列表更新逻辑

核心问题是userInfoQuery.data更新时触发useEffect清理函数断开Socket,需将Socket生命周期与好友列表更新解耦:

修改App.js中的useEffect:

// 仅在token变化时初始化/销毁Socket(登录、登出场景)
useEffect(() => {
  let socketInstance = null;
  if (token && userInfoQuery.isSuccess && userInfoQuery.data) {
    socketInstance = initializeSocket(token, userInfoQuery.data, setOnlineFriends);
    setSocket(socketInstance);
  }

  return () => {
    if (socketInstance) {
      socketInstance.disconnect();
    }
  };
}, [token, userInfoQuery.isSuccess, userInfoQuery.data]);

// 单独处理好友列表更新,仅在好友列表变化时发送Socket事件
useEffect(() => {
  if (socket && userInfoQuery.data?.friends) {
    socket.emit("userFriends", userInfoQuery.data.friends);
  }
}, [socket, userInfoQuery.data?.friends]);

2. 优化initializeSocket函数

避免重复绑定Socket事件,仅在创建实例时绑定一次:

export const initializeSocket = (token, userInfoQuery, setOnlineFriends) => {
  if (!token || !userInfoQuery) return null;

  const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });

  // 绑定永久事件监听
  socket.on("onlineUsers", (users) => {
    setOnlineFriends(users);
  });

  socket.on("addFriend", (friend) => {
    console.log("Received friend request:", friend);
  });

  socket.on("removeFriend", (friend) => {
    console.log("Received friend request:", friend);
  });

  // 初始发送好友列表
  socket.emit("userFriends", userInfoQuery.friends);

  return socket;
};

3. 服务端优化建议

  • 修复addFriend/removeFriend逻辑:当前用户对象无socket_id,需在onlineFriends中存储socket.id
    // 连接时更新存储内容
    onlineFriends.set(userId, { ...user, socket_id: socket.id });
    
  • 优化在线列表推送范围:socket.broadcast.emit会推送给所有用户,应改为仅推送给当前用户的好友列表,减少无效推送
  • 断开连接时的推送逻辑同步优化,仅通知相关好友

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 22:54:55