ReactJS/React Native使用Socket最佳实践及性能优化问题咨询
React 生态 Socket 连接生产级最佳实践
1. Socket 实例全局共享方案
不要在组件内部初始化Socket实例,避免重复创建连接、资源泄漏问题,优先采用顶层模块单例模式实现实例全局复用:
// src/utils/socket.ts 全局实例模块 import { io, Socket } from 'socket.io-client'; let socketInstance: Socket | null = null; export const getSocket = (): Socket => { if (!socketInstance) { socketInstance = io(process.env.REACT_APP_SOCKET_ENDPOINT!, { transports: ['websocket'], reconnection: true, reconnectionAttempts: 10, reconnectionDelay: 1000, // 生产环境统一挂载鉴权信息 auth: { token: localStorage.getItem('access_token') }, }); // 全局统一处理连接错误、重连逻辑,不要散落在业务组件中 socketInstance.on('connect_error', (err) => console.error('Socket连接异常:', err)); } return socketInstance; }; // 用户登出、权限变更时调用,销毁旧实例避免权限泄漏 export const destroySocket = () => { if (socketInstance) { socketInstance.disconnect(); socketInstance = null; } };
注意:不要把Socket实例存入React State或Context Value,实例本身是可变引用,不需要响应式更新,存入状态会导致不必要的全局重渲染。如果需要全局响应式感知连接状态,可以单独把
isConnected布尔值存入Context,和实例本身分离。
2. 订阅/监听逻辑的Hooks封装
统一封装订阅钩子,自动处理监听卸载、闭包陷阱问题,避免重复写模板代码:
// src/hooks/useSocketSubscription.ts import { useEffect, useRef } from 'react'; import { getSocket } from '@/utils/socket'; type EventCallback = (...args: any[]) => void; export const useSocketSubscription = ( eventName: string, callback: EventCallback, // 可选:回调依赖的动态参数,和useEffect依赖逻辑一致 deps: React.DependencyList = [] ) => { // 用ref存储最新回调,避免回调变化导致重复订阅/取消订阅 const callbackRef = useRef<EventCallback>(callback); // 回调更新时自动刷新ref,不会触发重新订阅 useEffect(() => { callbackRef.current = callback; }, [callback, ...deps]); useEffect(() => { const socket = getSocket(); const handler = (...args: any[]) => callbackRef.current(...args); socket.on(eventName, handler); // 组件卸载时自动移除监听,避免内存泄漏 return () => socket.off(eventName, handler); }, [eventName]); };
3. 监听回调更新State的性能优化
这是重渲染问题的核心优化点,可按优先级采用以下方案:
- 状态按需拆分:不要把所有Socket推送数据存在同一个顶层State,按业务模块拆分状态,只有对应订阅的组件才会触发重渲染
- 跳过无意义更新:更新State前先做数据对比,没有变化时直接返回旧State,不触发重渲染:
useSocketSubscription('order:update', (newOrder) => { setOrders(prevOrders => { const targetIdx = prevOrders.findIndex(item => item.id === newOrder.id); if (targetIdx < 0) return prevOrders; // 浅/深对比数据,无变化直接返回旧值 if (JSON.stringify(prevOrders[targetIdx]) === JSON.stringify(newOrder)) return prevOrders; // 仅更新变化的项 const newOrders = [...prevOrders]; newOrders[targetIdx] = newOrder; return newOrders; }); }, []); - 高频推送节流/防抖:针对实时行情、进度条这类每秒推送多次的场景,控制更新频率:
import { useCallback } from 'react'; import throttle from 'lodash.throttle'; // 最多500ms更新一次行情数据 const handleQuoteUpdate = useCallback(throttle((quote) => { setLatestQuote(quote); }, 500), []); useSocketSubscription('quote:push', handleQuoteUpdate); - 长列表虚拟化:如果推送数据需要渲染为长列表,采用
react-window等虚拟化组件,仅渲染可视区域内容,大幅降低重渲染开销。
4. 生产环境额外注意事项
- 事件名按业务模块命名,比如
payment:success、notify:new,方便问题排查和统一管理 - 监听回调中不要执行 heavy 计算,复杂逻辑迁移到Web Worker处理,避免阻塞主线程
- 监听页面显隐状态,页面切后台时暂停非必要订阅,切回前台再恢复,降低服务端压力和客户端性能消耗
- 统一实现心跳检测逻辑,在单例初始化时统一挂载,避免假死连接
- 路由切换时自动清理当前页面相关的订阅,避免离开页面后还在处理无关推送。
内容的提问来源于stack exchange,提问作者Fábio BC Souza
相关产品推荐
相关产品推荐

