WebSocket 基础先搞懂?
想在 Vue3 项目里做实时聊天、股票行情推送这类功能,WebSocket 肯定绕不开,但刚接触时,不少同学会犯难:咋在 Vue3 里初始化连接?消息收发咋和响应式数据结合?断开重连咋处理?今天用问答方式,把 Vue3 + WebSocket 从基础到实战掰碎了讲,新手也能跟着做。
先别急着写代码,得先明白 WebSocket 是干啥的,它是 HTML5 提供的**全双工通信协议**,能让客户端和服务端随时双向发消息,和 HTTP 有啥不一样?HTTP 是“请求 - 响应”模式,服务端没法主动给客户端发消息;但 WebSocket 建立连接后,两边想发就发,特别适合实时性高的场景(比如聊天、实时数据监控)。 举个例子:你刷短视频 App 时,有人给你发评论通知,这背后可能就是 WebSocket 让服务端主动把“新评论”消息推给你,要是用 HTTP,得不停轮询(比如每隔几秒发请求问有没有新消息),既费流量又延迟高,WebSocket 就解决了这痛点。Vue3 项目里怎么初始化 WebSocket 连接?
在 Vue3 里用 WebSocket,核心是创建 WebSocket 实例,再和 Vue 的生命周期、响应式数据结合,咱分步骤看:
创建 WebSocket 实例
WebSocket 是浏览器原生 API,直接 new 就行,假设服务端地址是 ws://xxx
,在 Vue3 的组件里(用组合式 API 举例),可以这么写:
<script setup> import { onMounted, onUnmounted, ref } from 'vue' const ws = ref(null) // 存WebSocket实例 onMounted(() => { // 初始化连接 ws.value = new WebSocket('ws://你的服务端地址') // 监听连接成功 ws.value.onopen = () => { console.log('WebSocket 连接成功') // 连接成功后可以发初始化消息,比如用户身份 ws.value.send(JSON.stringify({ type: 'login', userId: 'xxx' })) } // 监听连接错误 ws.value.onerror = (err) => { console.error('WebSocket 连接出错:', err) } }) onUnmounted(() => { // 组件销毁时关闭连接,避免内存泄漏 if (ws.value) { ws.value.close() } }) </script>
这里用 onMounted
初始化(组件挂载后建立连接),onUnmounted
销毁时关闭连接,用 ref
存实例,是为了让 Vue 能追踪它的状态变化。
注意服务端兼容性
前端能连,得服务端也支持 WebSocket 协议,Node.js 用 ws
库、Java 用 Netty 这类框架都能搭 WebSocket 服务,要是你本地测试,也能找些公开的 WebSocket 测试地址(ws://echo.websocket.org
是经典的回声测试服务,发消息会原样返回)。
怎么处理消息收发?
连接建好了,接下来是发消息和收消息,还要把收到的消息同步到 Vue 的响应式数据里,让界面自动更新。
发消息:主动给服务端发数据
WebSocket 实例的 send()
方法负责发消息,消息可以是字符串或二进制数据,实际开发常用 JSON 字符串(方便结构化数据),比如做聊天功能,用户输入内容后发消息:
<template> <input v-model="inputMsg" placeholder="请输入消息" /> <button @click="sendMsg">发送</button> </template> <script setup> import { ref } from 'vue' const inputMsg = ref('') const ws = ref(null) const sendMsg = () => { if (ws.value && ws.value.readyState === WebSocket.OPEN) { // 确保连接已打开 ws.value.send(JSON.stringify({ type: 'chat', content: inputMsg.value })) inputMsg.value = '' // 清空输入框 } else { alert('连接未就绪,请稍等') } } </script>
收消息:服务端推消息到前端
用 onmessage
监听服务端发来的消息,收到后,把消息存到响应式数据里(messages
列表),界面就会自动更新:
<script setup> import { ref, onMounted } from 'vue' const messages = ref([]) // 存所有聊天消息 const ws = ref(null) onMounted(() => { ws.value = new WebSocket('ws://xxx') ws.value.onmessage = (event) => { // event.data 是服务端发来的消息 const msg = JSON.parse(event.data) messages.value.push(msg) // 响应式数据更新,界面自动渲染 } }) </script> <template> <div v-for="(msg, index) in messages" :key="index"> {{ msg.content }} </div> </template>
这里要注意,onmessage
里的操作要简洁,别做太耗时的事,不然影响性能,如果消息处理复杂,可以拆成函数调用。
连接状态管理有啥技巧?
WebSocket 连接很“脆弱”——网络波动、服务端重启都会导致断开,所以得做心跳保活和自动重连,让连接更稳定。
心跳机制:防止连接被网关断开
有些服务器或中间网关(比如公司内网、nginx)会把长时间没动静的连接断开,心跳就是定时给服务端发个“我还活着”的消息,保持连接。
在 Vue3 里可以用 setInterval
实现:
<script setup> import { onMounted, onUnmounted, ref } from 'vue' const ws = ref(null) let heartBeatTimer = null onMounted(() => { ws.value = new WebSocket('ws://xxx') // 连接成功后启动心跳 ws.value.onopen = () => { heartBeatTimer = setInterval(() => { if (ws.value.readyState === WebSocket.OPEN) { ws.value.send(JSON.stringify({ type: 'heartbeat' })) } }, 30 * 1000) // 每30秒发一次心跳 } }) onUnmounted(() => { clearInterval(heartBeatTimer) // 组件销毁时清除定时器 if (ws.value) ws.value.close() }) </script>
自动重连:断网后自动恢复连接
当连接意外断开(比如用户切到弱网环境),得自动尝试重连,可以监听 onclose
事件,结合延迟重连策略:
<script setup> import { onMounted, onUnmounted, ref } from 'vue' const ws = ref(null) let reconnectTimer = null const reconnect = () => { reconnectTimer = setTimeout(() => { ws.value = new WebSocket('ws://xxx') // 重连后要重新绑定事件(比如onmessage、onopen) ws.value.onopen = () => { clearTimeout(reconnectTimer) // 重连成功,清除重连定时器 console.log('WebSocket 重连成功') } ws.value.onclose = () => { reconnect() // 连接又断了,继续重连 } }, 5 * 1000) // 5秒后重试 } onMounted(() => { ws.value = new WebSocket('ws://xxx') ws.value.onclose = () => { console.log('WebSocket 连接断开,开始重连') reconnect() } }) onUnmounted(() => { clearTimeout(reconnectTimer) if (ws.value) ws.value.close() }) </script>
这样即使网络波动,也能自动恢复连接,用户几乎没感知。
实战:做个简易实时聊天组件
光讲理论不够,咱动手做个能收发消息的聊天组件,把前面的知识点串起来。
步骤1:搭建界面结构
<template> <div class="chat-container"> <!-- 消息列表 --> <div class="messages"> <div v-for="(msg, index) in messages" :key="index" :class="['msg-item', { self: msg.isSelf }]" > <span class="sender">{{ msg.sender }}:</span> <span class="content">{{ msg.content }}</span> </div> </div> <!-- 输入区域 --> <div class="input-area"> <input v-model="inputMsg" placeholder="输入消息后按回车发送" @keyup.enter="sendMsg" /> <button @click="sendMsg">发送</button> </div> </div> </template>
步骤2:编写逻辑代码
<script setup> import { ref, onMounted, onUnmounted } from 'vue' // 响应式数据 const messages = ref([]) // 消息列表 const inputMsg = ref('') // 输入框内容 const ws = ref(null) // WebSocket实例 // 初始化WebSocket onMounted(() => { // 这里用公开测试服务,实际替换成自己的服务端地址 ws.value = new WebSocket('ws://echo.websocket.org') // 连接成功 ws.value.onopen = () => { console.log('连接成功,可发送消息') // 模拟发送登录消息(实际项目传用户ID等) ws.value.send(JSON.stringify({ type: 'login', userId: 'vue3_user' })) } // 接收消息 ws.value.onmessage = (event) => { const res = JSON.parse(event.data) // 测试服务会原样返回,这里模拟区分自己和对方消息 messages.value.push({ sender: res.isSelf ? '我' : '对方', content: res.content, isSelf: res.isSelf }) } // 连接错误 ws.value.onerror = (err) => { console.error('WebSocket出错:', err) alert('连接失败,请检查网络') } // 连接关闭(自动重连逻辑可参考前面的方法添加) ws.value.onclose = () => { console.log('连接已关闭') } }) // 发送消息 const sendMsg = () => { if (!inputMsg.value.trim()) return alert('请输入内容') if (ws.value && ws.value.readyState === WebSocket.OPEN) { const msg = { type: 'chat', content: inputMsg.value, isSelf: true // 标记是自己发的消息 } ws.value.send(JSON.stringify(msg)) inputMsg.value = '' // 清空输入框 } else { alert('连接未就绪,请稍等') } } // 组件销毁时关闭连接 onUnmounted(() => { if (ws.value) { ws.value.close() } }) </script>
步骤3:加简单样式(可选)
<style scoped> .chat-container { width: 300px; margin: 20px auto; border: 1px solid #eee; border-radius: 8px; padding: 10px; } .messages { height: 300px; overflow-y: auto; margin-bottom: 10px; } .msg-item { margin: 5px 0; padding: 4px 8px; border-radius: 4px; } .msg-item.self { background: #e3f2fd; margin-left: auto; } .input-area { display: flex; } .input-area input { flex: 1; padding: 6px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; } .input-area button { padding: 0 16px; border: none; background: #42b983; color: #fff; border-radius: 0 4px 4px 0; cursor: pointer; } </style>
现在运行组件,输入消息按回车,服务端会原样返回,消息列表里能看到自己和“对方”的消息(因为测试服务会回声,这里用isSelf
标记区分),实际项目里,服务端会把消息转发给其他用户,逻辑类似。
性能和兼容性要注意啥?
代码跑通了,还要考虑生产环境的坑:
浏览器兼容性
WebSocket 是 HTML5 特性,现代浏览器(Chrome、Edge、Firefox、Safari)都支持,但老旧浏览器(IE)完全不支持,如果要兼容旧版,得用 Socket.IO 这类带 fallback 的库(但 Vue3 项目里用的话,要注意和框架的结合)。
连接数和消息大小
- 单个客户端一般别同时连太多 WebSocket 服务(浏览器对同域名的连接数有限制,Chrome 是 6 个左右)。
- 消息别太大!WebSocket 虽然后台是 TCP,但消息过大容易触发延迟或断开,建议拆分大消息,或者用二进制格式压缩。
Vue3 响应式优化
如果消息量极大(比如实时行情每秒几十条),直接往 ref
或 reactive
里 push 消息会触发频繁更新,影响性能,可以用 shallowRef
或者批量更新(比如攒一批消息再渲染)。
import { shallowRef, nextTick } from 'vue' const messages = shallowRef([]) let tempMessages = [] // 临时数组存消息 ws.value.onmessage = (event) => { tempMessages.push(JSON.parse(event.data)) // 每100ms批量更新一次 setTimeout(() => { messages.value = [...tempMessages] tempMessages = [] }, 100) }
这样能减少响应式更新次数,提升性能。
现在再回头看,Vue3 里用 WebSocket 其实是“浏览器原生 API + Vue 响应式 + 业务逻辑”的结合,从初始化连接、收发消息,到心跳保活、自动重连,每个环节都要兼顾功能和稳定性,把今天的知识点落地到项目里,不管是做聊天、IM 还是实时数据看板,思路都通了,要是你在实践中碰到服务端配置、跨域这些问题,下次可以专门聊聊 WebSocket 服务端搭建和前端跨域处理~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。