不能直接在组件里 new websocket,因其原生 api 不自带重连、心跳、消息队列等容错能力,易导致静默断开、重复连接、invalidstateerror 等问题;必须封装为 usewebsocket composable,统一管理连接生命周期、状态监听、自动重连(带 cleartimeout 防堆积)、readystate 校验及 keepalive 场景适配。

WebSocket 连接不是“建了就完事”的东西——它极易因网络抖动、页面切换、服务端重启而断开,且原生 API 不自带重连、心跳、消息队列等能力。直接在 onMounted 里 new WebSocket 然后发消息,上线后大概率会遇到「消息收不到」「连接静默断开不重试」「重复连接」「send 报错 InvalidStateError」等问题。
为什么不能直接在组件里 new WebSocket?
原生 WebSocket 是低阶接口,暴露太多状态细节,却没封装常见容错逻辑:
-
readyState变化需手动监听,但onopen/onclose触发时机与实际网络状态可能不同步 - 断线后不会自动重连,
onclose里setTimeout(connect, 3000)容易失控(比如多次触发、未清定时器) - 组件卸载时若忘记
ws.close(),连接会残留;若卸载太快,ws可能还没初始化完成 - 多个组件共用同一连接时,无法共享
messages或连接状态,造成重复实例
推荐用 useWebSocket 封装成可复用 Composable
把连接生命周期、消息收发、错误恢复都收进一个函数里,其他组件只管调用,不操心底层细节。下面是一个生产可用的简化版(基于 Composition API + TypeScript):
import { ref, onUnmounted, onDeactivated, onActivated } from 'vue'
export function useWebSocket(url: string) {
const socket = ref<websocket null>(null)
const messages = ref<any>([])
const isConnected = ref(false)
const reconnectTimer = ref<nodejs.timeout null>(null)
const connect = () => {
if (socket.value?.readyState === WebSocket.OPEN) return
socket.value = new WebSocket(url)
socket.value.onopen = () => {
isConnected.value = true
if (reconnectTimer.value) {
clearTimeout(reconnectTimer.value)
reconnectTimer.value = null
}
console.log('✅ WebSocket connected')
}
socket.value.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
messages.value.push(data)
} catch (e) {
messages.value.push(event.data) // 非 JSON 消息也保留
}
}
socket.value.onerror = (err) => {
console.error('⚠️ WebSocket error:', err)
isConnected.value = false
}
socket.value.onclose = () => {
isConnected.value = false
console.warn('❌ WebSocket closed, will retry in 3s')
reconnectTimer.value = setTimeout(connect, 3000)
}
}
const send = (data: any) => {
if (socket.value?.readyState === WebSocket.OPEN) {
socket.value.send(JSON.stringify(data))
} else {
console.warn('⚠️ WebSocket not ready, drop message:', data)
}
}
const close = () => {
socket.value?.close()
if (reconnectTimer.value) {
clearTimeout(reconnectTimer.value)
reconnectTimer.value = null
}
}
// 组件激活/失活时控制连接(用于 keep-alive 场景)
onActivated(connect)
onDeactivated(close)
onUnmounted(close)
connect() // 初始化连接
return {
isConnected,
messages,
send,
close
}
}
</nodejs.timeout></any></websocket>
注意几个关键点:
- 用
onActivated/onDeactivated支持<keepalive></keepalive>场景,避免后台 tab 断连后不恢复 -
reconnectTimer必须显式清除,否则组件反复挂载会堆积多个重连定时器 -
send前检查readyState,避免InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state - 对
onmessage的JSON.parse做了 try/catch,防止服务端发非 JSON 数据导致整个消息流中断
在组件中使用时别漏掉 onUnmounted 清理
即使用了 Composable,如果组件本身没正确响应销毁周期,仍可能泄漏连接。尤其在路由跳转或 v-if 切换频繁时:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
<script setup>
import { useWebSocket } from '@/composables/useWebSocket'
const { messages, send, close } = useWebSocket('ws://localhost:3000')
// ✅ 正确:Composable 内部已处理 onUnmounted
// ❌ 错误:再额外写 close() —— 会重复关闭,报错 "WebSocket is already in CLOSING or CLOSED state"
const handleSubmit = () => {
send({ type: 'chat', content: 'hello' })
}
</script>
常见误操作:
- 在组件内再次调用
close()—— Composable 已注册onUnmounted,重复关会抛异常 - 把
useWebSocket写在onMounted里 —— 失去响应式绑定,messages不更新 UI - 传入的
url是硬编码字符串,没走环境变量(如import.meta.env.VITE_WS_URL),导致测试/生产地址不一致
服务端地址必须是 ws:// 或 wss://,且跨域要配好
浏览器强制校验协议和域名,常见错误:
-
Failed to construct 'WebSocket': The URL's scheme must be either 'ws' or 'wss'→ 检查 URL 是不是写了http:// -
WebSocket connection to 'ws://localhost:3000' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED→ 后端没启动,或端口被占 -
WebSocket connection to 'wss://xxx' failed: Error during WebSocket handshake: Unexpected response code: 403→ Nginx/反代没透传Upgrade和Connectionheader
如果是本地开发,确保后端已开启 WebSocket 服务,并允许前端域名(如 http://localhost:5173)跨源连接;生产环境务必用 wss://,且证书有效。
真正难的不是连上,而是连得稳、断得明、重得准、发得对——这些细节藏在每次 onclose 的判断里,藏在每个 send 前的 readyState 检查里,也藏在你有没有给 reconnectTimer 加 clearTimeout 里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










