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

React useContext导致页面加载时重置WebSocket连接问题求助

解决Next.js中WebSocket页面刷新后连接重置、退出房间的问题

页面刷新时客户端应用会完全重启,原有Socket连接断开,同时客户端Socket实例被重新创建,导致丢失房间状态。以下是具体解决方案:

一、确保客户端Socket实例单例化

当前直接在模块顶层创建Socket实例,页面刷新时模块重新加载会生成新连接,改成单例模式避免重复创建:

修改/contexts/socket.tsx:

import socketio from "socket.io-client";
import React from "react";

// 单例存储Socket实例
let socketInstance: ReturnType<typeof socketio> | null = null;

export const getSocketInstance = () => {
  if (!socketInstance) {
    socketInstance = socketio({
      // 开启自动重连配置
      reconnection: true,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 5000,
      reconnectionAttempts: Infinity
    });
  }
  return socketInstance;
};

export const SocketContext = React.createContext(getSocketInstance());

更新pages/_app.tsx使用单例:

import { SocketContext, getSocketInstance } from '../contexts/socket';

const App = ({ Component, pageProps }: AppProps) => {
    const socket = getSocketInstance();

    return (
        <SocketContext.Provider value={socket}>
            <main>
                <Component {...pageProps} key={router.asPath} />
                <Toaster position="bottom-center" toastOptions={{ duration: 3000, }} />
            </main>
        </SocketContext.Provider>
    )
};

export default wrapper.withRedux(App);

二、页面刷新后自动恢复房间状态

将用户当前所在房间ID存入本地存储,页面加载后自动触发加入房间操作:

修改pages/chat/index.tsx:

import { useState, useContext, useEffect } from "react";
import AppLayout from "../../layouts/AppLayout";
import useUser from '../../hooks/useUser';
import { SocketContext } from '../../contexts/socket';

// 定义Message类型
type Message = {
  author: string;
  message: string;
};

export default function Chat() {
  const username = useUser().username;
  const [messages, setMessages] = useState<Array<Message>>([]);
  const socket = useContext(SocketContext);
  // 实际项目中替换为真实房间ID(比如从路由参数获取)
  const roomId = 'target-room-id';

  useEffect(() => {
    // 页面加载时读取本地存储的房间ID,自动加入
    const savedRoom = localStorage.getItem('current-chat-room');
    if (savedRoom) {
      socket.emit('joinRoom', savedRoom);
    }

    // 订阅消息事件,使用命名函数方便后续取消订阅
    const handleNewMessage = (msg: any) => {
      setMessages(prev => [...prev, { author: msg.author, message: msg.message }]);
    };
    socket.on("newIncomingMessage", handleNewMessage);

    // 加入目标房间并保存到本地存储
    socket.emit('joinRoom', roomId);
    localStorage.setItem('current-chat-room', roomId);

    // 组件卸载时取消事件订阅,避免内存泄漏
    return () => {
      socket.off("newIncomingMessage", handleNewMessage);
    };
  }, [socket, roomId])

  return (
    <AppLayout>
      {/* 聊天界面渲染逻辑 */}
    </AppLayout>
  )
}

三、服务器端配合处理重连逻辑

在服务器端维护用户与房间的映射,处理客户端重连时自动恢复房间状态:

修改pages/api/socket.tsx:

import { Server } from "socket.io";

// 存储socket.id与房间ID的映射,生产环境可替换为Redis
const socketRoomMap = new Map<string, string>();

export default function SocketHandler(req: any, res: any) {
  if (res.socket.server.io) {
    console.log("server socket already set up");
    res.end();
    return;
  }

  const io = new Server(res.socket.server, {
    cors: { origin: process.env.NEXT_PUBLIC_APP_URL || "*" }
  });
  res.socket.server.io = io;

  const onConnection = (socket: any) => {
    console.log('server new socket.id: ', socket.id);
    
    // 重连时自动加入之前的房间
    const prevRoom = socketRoomMap.get(socket.id);
    if (prevRoom) {
      socket.join(prevRoom);
      console.log(`Socket ${socket.id} reconnected to room ${prevRoom}`);
    }

    messageHandler(io, socket);

    // 断开连接时暂存房间信息(5分钟后清除,避免内存占用)
    socket.on('disconnect', () => {
      const room = socketRoomMap.get(socket.id);
      if (room) {
        setTimeout(() => socketRoomMap.delete(socket.id), 5 * 60 * 1000);
      }
      console.log(`Socket ${socket.id} disconnected`);
    });
  };

  io.on("connection", onConnection);
  res.end();
}

function messageHandler(io: any, socket: any) {
  const createdMessage = (msg: any) => {
    socket.broadcast.emit("newIncomingMessage", msg);
  };

  // 处理加入房间事件,保存映射关系
  const joinRoom = (roomId: string) => {
    socket.join(roomId);
    socketRoomMap.set(socket.id, roomId);
    console.log(`Socket ${socket.id} joined room ${roomId}`);
  };

  socket.on("createdMessage", createdMessage);
  socket.on("joinRoom", joinRoom);
};

核心优化点总结

  • 单例模式保证客户端Socket实例唯一,避免页面刷新时创建新连接
  • 本地存储+服务器端映射,实现页面刷新后自动恢复房间状态
  • 组件内管理事件订阅的生命周期,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 08:23:55