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

React17 + Socket.IO4 自定义钩子触发客户端两次连接问题

问题根因

  1. React官方明确规定useMemo仅作为性能优化手段,不保证缓存永久有效,内存不足等场景下React会主动销毁useMemo缓存触发重新计算,一旦重新执行io()方法就会创建新的socket连接,这是两次连接的核心原因。
  2. 若开启了React严格模式(<StrictMode>),开发环境下组件会故意重复渲染两次做副作用校验,也会触发useMemo重新执行,生成第二个socket实例。
  3. 你之前用useEffect出现的Cannot read properties of undefined (reading 'on')报错,是因为首次渲染时socket的初始值为undefined,监听connect的useEffect执行时机早于初始化socket的useEffect赋值时机,所以读取不到on方法。

解决方案

推荐方案:useRef + useEffect 封装(无重复连接、自动清理)

该方案可以保证socket实例全局唯一,自动处理监听绑定、断开清理,不会出现报错:

// useSocket.ts
import { io, Socket } from "socket.io-client";
import { useEffect, useRef, useState } from "react";

export const useSocket = (serverPath: string) => {
  const socketRef = useRef<Socket | null>(null);
  const [online, setOnline] = useState<boolean>(false);

  useEffect(() => {
    // 仅在serverPath变化时初始化一次socket
    const socketInstance = io(serverPath, { transports: ["websocket"] });
    socketRef.current = socketInstance;

    // 绑定连接状态监听
    const handleConnect = () => setOnline(true);
    const handleDisconnect = () => setOnline(false);
    socketInstance.on("connect", handleConnect);
    socketInstance.on("disconnect", handleDisconnect);

    // 组件卸载/路径变化时清理资源
    return () => {
      socketInstance.off("connect", handleConnect);
      socketInstance.off("disconnect", handleDisconnect);
      socketInstance.disconnect();
      socketRef.current = null;
    };
  }, [serverPath]);

  return {
    socket: socketRef.current,
    online
  };
};

SocketContext.tsx和App.tsx保持你原有代码即可,后续在业务组件调用socket方法时加一层非空判断即可避免报错:

const { socket } = useContext(SocketContext);
if (socket) {
  socket.emit("your_event", payload);
}

无useEffect方案(仅作参考)

如果你确实不想使用useEffect,可以用React state惰性初始化的特性保证socket仅创建一次,注意该方案在开发环境开启严格模式时仍会执行两次初始化,生产环境正常:

// useSocket.ts
import { io, Socket } from "socket.io-client";
import { useState } from "react";

export const useSocket = (serverPath: string) => {
  // 惰性初始化state,传入的函数只会执行一次
  const [socket] = useState<Socket>(() => io(serverPath, { transports: ["websocket"] }));
  const [online, setOnline] = useState<boolean>(false);

  // 直接绑定监听,socket已初始化不存在undefined问题
  socket.on("connect", () => setOnline(true));
  socket.on("disconnect", () => setOnline(false));

  return { socket, online };
};

内容的提问来源于stack exchange,提问作者S. Wasta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 16:45:03