Vue3中直接在setup()里new WebSocket()会导致重复连接、内存泄漏及状态错误,正确做法是封装成组合式函数,用onUnmounted清理并实现自动重连。

Vue 3 里直接用原生 WebSocket 是可行的,但必须手动处理重连、状态同步、组件卸载时的清理——不封装会很快失控。
为什么不能在 setup() 里直接 new WebSocket()
因为 WebSocket 实例是长期存活的,而组件可能频繁挂载/卸载。如果只在 setup() 里创建却不销毁,会出现:
- 重复连接(多个组件实例共存时)
- 内存泄漏(
onmessage回调仍绑定,但组件已销毁) - 报错
WebSocket is already in CLOSING or CLOSED state
正确做法:把连接逻辑抽离成可复用的组合式函数,用 onUnmounted 清理连接。
如何封装一个带自动重连的 useWebSocket
核心是控制连接生命周期 + 避免重复触发。关键点:
立即学习“前端免费学习笔记(深入)”;
- 用
ref存储WebSocket实例,避免多次 new - 用
setTimeout做指数退避重连(首次 1s,失败后 2s、4s…上限 30s) - 在
onUnmounted中调用close(),并清除定时器 - 用
computed暴露status(CONNECTING/OPEN/CLOSED)方便模板判断
示例精简版:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
import { ref, onUnmounted, computed } from 'vue'
<p>export function useWebSocket(url) {
const socket = ref(null)
const status = ref(WebSocket.CONNECTING)
let reconnectTimer = null</p><p>const connect = () => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) return
socket.value = new WebSocket(url)</p><pre class='brush:php;toolbar:false;'>socket.value.onopen = () => {
status.value = WebSocket.OPEN
clearTimeout(reconnectTimer)
}
socket.value.onclose = () => {
status.value = WebSocket.CLOSED
reconnectTimer = setTimeout(connect, Math.min(30000, (reconnectTimer ? 2 * (reconnectTimer._delay || 1000) : 1000)))
}
socket.value.onerror = (e) => console.error('WS error:', e)}
const send = (data) => { if (socket.value?.readyState === WebSocket.OPEN) { socket.value.send(JSON.stringify(data)) } }
onUnmounted(() => { socket.value?.close() clearTimeout(reconnectTimer) })
return { socket, status: computed(() => status.value), send, connect } }
在组件中安全使用 useWebSocket
注意两个易错点:
- 不要在
onMounted里调用connect()—— 组合式函数内部已做连接管理,直接调用connect()即可 - 发送消息前必须检查
status.value === WebSocket.OPEN,否则会报InvalidStateError - 接收消息建议用
watch监听socket.value?.onmessage,或在onopen里统一注册(避免多次绑定)
组件内典型用法:
import { useWebSocket } from './useWebSocket'
<p>export default {
setup() {
const ws = useWebSocket('wss://echo.websocket.org')</p><pre class='brush:php;toolbar:false;'>// 启动连接
ws.connect()
// 发送示例
const sendMessage = () => {
if (ws.status.value === WebSocket.OPEN) {
ws.send({ type: 'ping', time: Date.now() })
}
}
// 接收消息(推荐在 onopen 后注册一次)
ws.socket.value?.onmessage = (e) => {
console.log('received:', JSON.parse(e.data))
}
return { ws, sendMessage }} }
生产环境必须加的防护措施
真实项目里光连上还不够:
- 鉴权:URL 后拼
?token=xxx,服务端校验;或改用headers(需服务端支持,浏览器原生WebSocket不支持自定义 header) - 心跳:每 30s 发
ping包,超时未收到pong则主动 close 并重连 - 消息序列化:前后端约定统一格式(如
{id, type, payload}),避免裸JSON.stringify导致解析失败 - 错误降级:当重试超过 5 次,提示用户“网络异常”,并提供手动重连按钮(调用
ws.connect())
WebSocket 不是“一劳永逸”的实时方案——连接状态不可靠、NAT 超时、代理中断都很常见。最麻烦的往往不是怎么发消息,而是怎么确认对方真的收到了。

















