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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:55:00