如何将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实例
解决方案
给两个可直接落地的方案,根据项目场景选择即可:
- 用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> } - 抽离全局socket单例,延迟初始化连接
如果多个组件都需要使用socket,不要把初始化逻辑塞在单个组件里,单独抽一个socket工具模块,不要在模块加载时就直接建立连接:
用的时候不管在哪个组件、哪个hook里,只要调用// 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; } }getSocket()就能拿到实例,而且只有第一次调用的时候才会初始化连接,这时候token肯定已经存入localStorage,不会出现取到null的问题。
额外提示:传递身份凭证优先使用socket.io提供的
auth配置项,不要放在query参数中,query参数会被服务端日志、代理层日志记录,泄露风险更高。
内容的提问来源于stack exchange,提问作者Monstar
相关产品推荐
相关产品推荐

