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
相关产品推荐
相关产品推荐

