React17 + Socket.IO4 自定义钩子触发客户端两次连接问题
问题根因
- React官方明确规定
useMemo仅作为性能优化手段,不保证缓存永久有效,内存不足等场景下React会主动销毁useMemo缓存触发重新计算,一旦重新执行io()方法就会创建新的socket连接,这是两次连接的核心原因。 - 若开启了React严格模式(
<StrictMode>),开发环境下组件会故意重复渲染两次做副作用校验,也会触发useMemo重新执行,生成第二个socket实例。 - 你之前用
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
相关产品推荐
相关产品推荐

