必须用useref持有websocket实例,因原生websocket是可变对象且生命周期独立于组件;若仅用usestate或闭包变量存储,会导致重复连接、onmessage重复绑定、卸载后setstate报错及“僵尸引用”问题。

直接在组件里 new WebSocket() + useEffect 初始化,不加封装,90% 的项目会在重连、状态不同步、卸载后 setState 报错上翻车。必须用 useRef 持有实例,useCallback 包裹发送/关闭逻辑,useEffect 只管初始化和清理。
为什么不能只靠 useState + useEffect
原生 WebSocket 是可变对象,生命周期独立于组件。如果只存到 useState 或闭包变量里:
-
useEffect在多次渲染后可能重复执行,new WebSocket()被调多次,后端看到一堆连接请求 -
onmessage回调被反复绑定,一条消息触发多次setMessages - 组件卸载后
onclose仍执行,setState报 “Can't perform a React state update on an unmounted component” - 局部变量
ws容易变成“僵尸引用”,发消息时调用的已是旧实例
useRef 持有实例 + useCallback 封装发送逻辑
这是避免竞态和内存泄漏的核心动作。所有对 WebSocket 实例的操作,必须通过 wsRef.current 访问:
本文档主要讲述的是React Native For Android 源码编译;希望对大家会有帮助;感兴趣的朋友可以过来看看
-
wsRef.current必须在useEffect内赋值,不能在函数体里提前 new - 发送前强制校验:
if (wsRef.current?.readyState === WebSocket.OPEN),否则丢弃并 warn,不抛异常 -
sendMessage和disconnect必须用useCallback包裹,防止每次渲染都生成新函数 - 清理函数中显式调用
wsRef.current?.close(),不能依赖自动 GC
如何安全处理 onclose 和重连
断开不等于要立刻重连——手动关闭和网络抖动需区别对待:
- 用
isManualClose = useRef(false)标记是否为用户主动断开 -
disconnect函数里设isManualClose.current = true,onclose中检查该标记,为false才启动重连 - 重连延迟从
1000ms 开始,失败后 ×2(1s → 2s → 4s → 8s),最多试 5 次 - 每次重连前先
clearTimeout(reconnectTimer.current),避免定时器堆积 - 别在
onerror里重连——它常在readyState === 0(CONNECTING)时误触发,应交由onclose统一判断
消息收发的类型与节流注意事项
真实环境里,event.data 类型不可信,高频操作也容易压垮连接:
- 接收时统一做类型守卫:
typeof event.data === 'string' ? JSON.parse(event.data) : null,外层套try/catch - 发送前不建议统一
JSON.stringify()—— 后端若要求原始字符串或需保留大整数精度(如 ID 超 16 位),这里会出 bug - 拖拽坐标、输入框同步等高频事件,必须节流:用
setTimeout+clearTimeout控制 1 秒内最多发 1 条 - 大文本或二进制消息,考虑加队列缓存,等
readyState === WebSocket.OPEN后再flush
最易被忽略的一点:不要用 isConnected 状态变量代替 wsRef.current?.readyState 做发送守卫——onopen 触发后,setIsConnected(true) 是异步的,中间存在时间差,此时发消息照样丢。










