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

如何将localStorage值作为query参数正确初始化socket.io?

实现目标


建立socket.io连接时自动携带localStorage中存储的token,完成用户身份校验。
最初参考官方指引,在MyComponent组件外部初始化io(),将token作为query参数传入,代码如下:

const token = localStorage.getItem('refresh_token');
const test = "hello world";

const socket = io(
  process.env.REACT_APP_API_ADDRESS_HOST,
  {
    query: { token }, // 取到值为null
  },
);

console.log(token) // 打印null
console.log(test) // 打印"hello world"


export const MyComponent = () => {
    // 业务逻辑
}

遇到的问题


上述代码写在组件外部,会在应用刚加载的初始化阶段执行,这时候localStorage里还未写入'refresh_token',因此取到的值永远是*null*,只有手动刷新页面才能拿到正确的token值。

已尝试的方案


试过把io()初始化逻辑挪到MyComponent组件内部,但存在两个明显问题:

  • 直接写在组件函数体里,每次组件重渲染都会重复执行初始化逻辑
  • 放在useEffect()里初始化的话,组件内其他函数、其他useEffect钩子没法直接访问生成的socket实例

解决方案

给两个可直接落地的方案,根据项目场景选择即可:

  1. 用useRef存储socket实例,保证全组件可访问+不会重复初始化
    把socket实例存在ref中,等token写入localStorage之后再执行连接。ref的特性是跨渲染周期保存值,不会因为重渲染丢失,组件内所有逻辑都能直接拿到实例:
    import { io } from 'socket.io-client';
    import { useRef, useEffect, useState } from 'react';
    
    export const MyComponent = () => {
      const socketRef = useRef(null);
      const [connected, setConnected] = useState(false);
    
      useEffect(() => {
        // 等token存在后再初始化连接,可直接判断token是否存在,也可在登录成功逻辑里触发初始化
        const token = localStorage.getItem('refresh_token');
        if (!token) return;
        // 已经初始化过socket就跳过,避免重复连接
        if (socketRef.current) return;
    
        socketRef.current = io(process.env.REACT_APP_API_ADDRESS_HOST, {
          query: { token }
        });
    
        // 正常绑定事件监听即可
        socketRef.current.on('connect', () => setConnected(true));
    
        // 组件卸载时断开连接,清理内存
        return () => {
          if (socketRef.current) {
            socketRef.current.disconnect();
            socketRef.current = null;
          }
        }
      }, [/* 可传入登录状态相关依赖,比如isLoggedIn */]);
    
      // 其他业务逻辑里直接通过socketRef.current就能拿到实例调用方法
      const sendMessage = (content) => {
        socketRef.current?.emit('send_msg', { content });
      }
    
      return <div>连接状态: {connected ? '已连接' : '未连接'}</div>
    }
    
  2. 抽离全局socket单例,延迟初始化连接
    如果多个组件都需要使用socket,不要把初始化逻辑塞在单个组件里,单独抽一个socket工具模块,不要在模块加载时就直接建立连接:
    // src/utils/socket.js
    import { io } from 'socket.io-client';
    
    let socket = null;
    
    export const getSocket = () => {
      // 已经初始化且处于连接状态就直接返回现有实例
      if (socket?.connected) return socket;
      
      const token = localStorage.getItem('refresh_token');
      socket = io(process.env.REACT_APP_API_ADDRESS_HOST, {
        // 新版本socket.io更推荐用auth字段传身份凭证,比query更安全,不会被日志记录
        auth: { token },
        reconnection: true,
      });
    
      // 自动重连时读取最新的token,避免token过期后重连失败
      socket.io.on("reconnect_attempt", () => {
        const latestToken = localStorage.getItem('refresh_token');
        socket.auth.token = latestToken;
      });
    
      return socket;
    }
    
    // 退出登录等需要断开连接的场景直接调用该方法即可
    export const closeSocket = () => {
      if (socket) {
        socket.disconnect();
        socket = null;
      }
    }
    
    用的时候不管在哪个组件、哪个hook里,只要调用getSocket()就能拿到实例,而且只有第一次调用的时候才会初始化连接,这时候token肯定已经存入localStorage,不会出现取到null的问题。

额外提示:传递身份凭证优先使用socket.io提供的auth配置项,不要放在query参数中,query参数会被服务端日志、代理层日志记录,泄露风险更高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 00:57:28