不能直接存 WebSocket 实例进 Vuex store,因其非响应式、不可观测,会导致响应式崩溃、内存泄漏、重复连接;应由独立 WebSocketManager 管理实例,Vuex 仅存 isConnected、messageList 等可序列化状态。

直接存 WebSocket 实例进 Vuex store 是危险操作,会导致响应式系统崩溃、内存泄漏、多组件重复连接——这不是“全局管理”,是全局埋雷。
为什么不能把 socket 直接放进 state
Vuex 的 state 依赖 Proxy 拦截属性访问,而 WebSocket 是原生事件对象,内部状态(如 readyState)不可被 Proxy 观测。你存进去的只是一个引用,但 Vuex 无法响应它的变化;更糟的是,当组件 unmount 后,这个引用还挂在 store 里,onmessage 回调仍可能触发,进而尝试更新已销毁的组件状态,报 Cannot read property 'xxx' of null。
-
socket不是响应式数据,存进state后无法用watch或computed安全监听其状态 - 多个组件调用
dispatch('initWebSocket')会反复新建连接,服务端看到一堆OPEN状态却只有一条真连接 - store 持有 socket 引用,即使所有组件都卸载了,GC 也无法回收它
socket 必须单例 + 外置管理,Vuex 只管“状态快照”
真正该进 Vuex 的只有可序列化、可观测、无副作用的字段:连接是否就绪、当前用户 ID、未读数、消息列表(数组)、最后心跳时间等。真实 WebSocket 实例应由一个独立的管理器持有,比如 WebSocketManager 类,它负责创建、重连、发包、收包,并通过 commit 向 store 同步关键状态。
- 在
src/utils/websocket-manager.ts中实例化唯一WebSocket,用class封装生命周期 - Vuex
state里只放isConnected: boolean、messageList: Array、pendingCount: number这类纯数据 - 所有
onmessage回调里不做 DOM 操作或组件逻辑,只做commit('RECEIVE_MESSAGE', payload) - 重连逻辑必须加锁(
lockReconnect: boolean),防止并发触发多次new WebSocket()
消息分发必须绕过 state 直接走事件总线或回调注册
别让每个组件都 watch(state.messages)。消息到达后,如果只靠 Vuex 响应式驱动,会因频繁 push 导致大量不必要的 diff 和渲染。高频聊天场景下,这会明显卡顿。
- 用
EventEmitter或mitt实现轻量事件总线,WebSocketManager收到消息后emitter.emit('chat:message', data) - 组件内用
onMounted(() => emitter.on('chat:message', handler))订阅,onUnmounted(() => emitter.off(...))清理 - Vuex 的
messageList仅用于持久化缓存或离线回溯,非实时渲染主通道 - 需要按类型分发?在
WebSocketManager内部用Map存> messageListeners,而不是塞进 store
最易被忽略的一点:心跳包的发送时机和响应处理不能依赖 Vuex mutation 的同步执行。它必须由 WebSocketManager 自己用 setInterval 驱动,并在 onmessage 中识别 PING/PONG 后立即更新本地时间戳——这个时间戳可以 commit 给 store 用于 UI 显示“最后在线时间”,但心跳本身绝不能被 store 的响应式链路拖慢。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











