WebSocket重连后readyState为3,组件通信失败问题求助
WebSocket重连后readyState异常与组件未更新问题分析与解决
问题背景
项目通过自定义Hook useWebSocket 维护单例WebSocket连接,已实现自动重连逻辑,但重连后调用handleDeposit发送消息时,socketInstance.readyState显示为3(CLOSED)(实际重连已完成);同时监听socketInstance的useEffect未触发更新。
核心问题原因
1. 组件持有的socketInstance是旧引用
useWebSocket中返回的socketInstance来自socketInstanceRef.current,重连时connectWebSocket创建了新的WebSocket实例并更新了socketInstanceRef.current和全局socketInstances,但Hook未通过状态更新通知组件。组件中socketInstance始终是初始渲染时的旧连接引用,所以显示的readyState仍是旧连接的CLOSED状态。
2. 监听socketInstance的useEffect未触发
ref.current的更新不会触发组件重渲染,组件中的socketInstance变量不会随ref.current变化而更新。依赖数组中的socketInstance始终是旧值,因此useEffect不会触发。
解决方案
第一步:修改useWebSocket Hook,用状态跟踪socket实例
将socket实例从ref改为React状态,确保实例更新时触发组件重渲染:
import { useWallet } from "@solana/wallet-adapter-react"; import React from "react"; const socketInstances: Record<string, WebSocket | null> = {}; // 提取socket配置为常量,避免硬编码 const SOCKET_CONFIG = { url: "socketURL", protocol: "protocol" }; export function useWebSocket() { const [initData, setInitData] = React.useState<any>(null); // 用state跟踪socket实例,更新时触发组件重渲染 const [socketInstance, setSocketInstance] = React.useState<WebSocket | null>(null); const messageListenerRef = React.useRef<((event: MessageEvent) => void) | null>(null); const { disconnect } = useWallet(); // 统一消息处理逻辑 const handleWebSocketMessage = React.useCallback((event: MessageEvent) => { try { const data = JSON.parse(event.data); if (!data || Object.keys(data).length === 0) return; if (data.action === "someAction") setInitData(data); if (data.description === "token not found in database") { disconnect(); localStorage.clear(); } } catch (error) { console.error("Error parsing WebSocket message:", error); } }, [disconnect]); // 创建新连接的逻辑 const connectWebSocket = React.useCallback(() => { const newSocket = new WebSocket(SOCKET_CONFIG.url, SOCKET_CONFIG.protocol); newSocket.onopen = () => { console.log(`Connected to WebSocket server: ${SOCKET_CONFIG.url}`); socketInstances[SOCKET_CONFIG.url] = newSocket; setSocketInstance(newSocket); }; newSocket.onclose = () => { console.log(`Disconnected from WebSocket server: ${SOCKET_CONFIG.url}`); setTimeout(connectWebSocket, 1000); }; // 绑定消息监听 if (messageListenerRef.current) { newSocket.addEventListener("message", messageListenerRef.current); } }, []); React.useEffect(() => { messageListenerRef.current = handleWebSocketMessage; // 初始化连接或复用已有实例 if (!socketInstances[SOCKET_CONFIG.url]) { connectWebSocket(); } else { setSocketInstance(socketInstances[SOCKET_CONFIG.url]); socketInstances[SOCKET_CONFIG.url]?.addEventListener("message", handleWebSocketMessage); } // 清理函数:移除消息监听 return () => { if (messageListenerRef.current && socketInstance) { socketInstance.removeEventListener("message", messageListenerRef.current); } }; }, [handleWebSocketMessage, connectWebSocket, socketInstance]); return { socketInstance, initData, }; } export { socketInstances };
第二步:组件中正确使用更新后的socketInstance
现在组件会在socket实例更新时自动重渲染,socketInstance会拿到最新引用,相关逻辑可正常工作:
const { socketInstance, initData } = useWebSocket(); // 监听socketInstance变化,重连后自动执行初始化逻辑 useEffect(() => { console.log("socketInstance updated:", socketInstance); if (socketInstance?.readyState === 1 && connected) { socketInstance.send( JSON.stringify({ action: "getserverinfo", token: user?.token, data: { user: { address: `${publicKey?.toBase58()}`, chainid: 0, }, }, }) ); // 使用addEventListener避免覆盖Hook中的全局消息监听 const handleServerInfo = (event: MessageEvent) => { const data = JSON.parse(event.data); if (data.action === "getserverinfo") { setAddressToDeposit(data.data?.server.address); } }; socketInstance.addEventListener("message", handleServerInfo); return () => socketInstance.removeEventListener("message", handleServerInfo); } }, [socketInstance, connected, publicKey, user?.token]); const handleDeposit = async () => { if (!publicKey) throw new WalletNotConnectedError(); if (!coinsToDeposit) throw new Error("Please enter a valid amount"); if (!addressToDeposit) throw new Error("Please refresh the page"); if (!socketInstance || socketInstance.readyState !== 1) throw new Error(`Please connect to the server`); transferUSDC(coinsToDeposit).then((signature) => { console.log(signature); // 再次检查连接状态,避免等待过程中连接断开 if (signature && socketInstance.readyState === 1) { socketInstance.send( JSON.stringify({ action: "deposit", token: user?.token, data: { user: { address: `${publicKey?.toBase58()}`, chainid: 0, }, amount: coinsToDeposit * 1000000, signature, }, }) ); } }); };
额外优化点
- 避免直接覆盖
socketInstance.onmessage,改用addEventListener防止Hook中的全局消息监听被组件覆盖; - 在异步操作回调中再次检查readyState,避免等待过程中连接断开;
- 将socket配置提取为常量,减少硬编码维护成本。
内容的提问来源于stack exchange,提问作者Talha
相关产品推荐
相关产品推荐

