上篇文章记录了如何在日常开发过程中引入并使用webSocket连接,但是在后续的开发过程中发现之前的写法有点问题,比如说多次引用连接会共用一个心跳,如果一个连接关掉了,后续其他的连接可能被一起关掉等等的bug。

(图片来源网络,侵删)
所以在这篇文章里针对上篇文章提供的方法进行改进,同时提供兼容vue3写法。
一、创建 WebSocket 类
class Socket { constructor(url, opts = {}) { this.url = url; this.ws = null; this.opts = { heartbeatInterval: 30000, // 默认30秒 reconnectInterval: 5000, // 默认5秒 maxReconnectAttempts: 5, // 默认尝试重连5次 ...opts }; this.reconnectAttempts = 0; this.listeners = {}; this.init(); } init() { this.ws = new WebSocket(this.url); this.ws.onopen = this.onOpen.bind(this); this.ws.onmessage = this.onMessage.bind(this); this.ws.onerror = this.onError.bind(this); this.ws.onclose = this.onClose.bind(this); } onOpen(event) { console.log('WebSocket opened:', event); this.reconnectAttempts = 0; // 重置重连次数 this.startHeartbeat(); this.emit('open', event); } onMessage(event) { console.log('WebSocket message received:', event.data); this.emit('message', event.data); } onError(event) { console.error('WebSocket error:', event); this.emit('error', event); } onClose(event) { console.log('WebSocket closed:', event); this.stopHeartbeat(); this.emit('close', event); if (this.reconnectAttempts { this.reconnectAttempts++; this.init(); }, this.opts.reconnectInterval); } } // 发送心跳 startHeartbeat() { this.heartbeatInterval = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send('ping'); // 可以修改为你的心跳消息格式 } }, this.opts.heartbeatInterval); } // 停止心跳 stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } send(data) { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(data); } else { console.error('WebSocket is not open. Cannot send:', data); } } on(event, callback) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); } off(event, callback) { if (!this.listeners[event]) return; const index = this.listeners[event].indexOf(callback); if (index !== -1) { this.listeners[event].splice(index, 1); } } emit(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(callback => callback(data)); } } } export default Socket;
我们首先定义一个 Socket 类,该类会负责与 WebSocket 服务器建立连接、发送和接收数据、以及管理心跳和重连逻辑。

(图片来源网络,侵删)
在你的Vue组件中使用这个类时,可以这样注册事件:
import Socket from './socket.js'; export default { data() { return { socket: null }; }, created() { this.socket = new Socket('ws://your-websocket-url'); this.socket.on('open', event => { console.log("Connected to server", event); }); this.socket.on('message', data => { console.log("Received data:", data); }); this.socket.on('error', error => { console.error("WebSocket Error:", error); }); this.socket.on('close', event => { console.log("Connection closed", event); }); }, beforeDestroy() { // 取消所有事件监听器 this.socket.off('open'); this.socket.off('message'); this.socket.off('error'); this.socket.off('close'); }, methods: { sendToServer(data) { this.socket.send(data); } } }
二、使用 Vue 3 的 Composition API
为了在 Vue 3 中更好地使用上述的 Socket 类,我们将其封装为一个 composable 函数,这样可以轻松地在任何 Vue 组件中使用 WebSocket。
import { ref, onUnmounted } from 'vue'; interface SocketOptions { heartbeatInterval?: number; reconnectInterval?: number; maxReconnectAttempts?: number; } class Socket { url: string; ws: WebSocket | null = null; opts: SocketOptions; reconnectAttempts: number = 0; listeners: { [key: string]: Function[] } = {}; heartbeatInterval: number | null = null; constructor(url: string, opts: SocketOptions = {}) { this.url = url; this.opts = { heartbeatInterval: 30000, reconnectInterval: 5000, maxReconnectAttempts: 5, ...opts }; this.init(); } init() { this.ws = new WebSocket(this.url); this.ws.onopen = this.onOpen.bind(this); this.ws.onmessage = this.onMessage.bind(this); this.ws.onerror = this.onError.bind(this); this.ws.onclose = this.onClose.bind(this); } onOpen(event: Event) { console.log('WebSocket opened:', event); this.reconnectAttempts = 0; this.startHeartbeat(); this.emit('open', event); } onMessage(event: MessageEvent) { console.log('WebSocket message received:', event.data); this.emit('message', event.data); } onError(event: Event) { console.error('WebSocket error:', event); this.emit('error', event); } onClose(event: CloseEvent) { console.log('WebSocket closed:', event); this.stopHeartbeat(); this.emit('close', event); if (this.reconnectAttempts { this.reconnectAttempts++; this.init(); }, this.opts.reconnectInterval); } } startHeartbeat() { if (!this.opts.heartbeatInterval) return; this.heartbeatInterval = window.setInterval(() => { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send('ping'); } }, this.opts.heartbeatInterval); } stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } send(data: string) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(data); } else { console.error('WebSocket is not open. Cannot send:', data); } } on(event: string, callback: Function) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); } off(event: string) { if (this.listeners[event]) { delete this.listeners[event]; } } emit(event: string, data: any) { this.listeners[event]?.forEach(callback => callback(data)); } } export function useSocket(url: string, opts?: SocketOptions) { const socket = new Socket(url, opts); onUnmounted(() => { socket.off('open'); socket.off('message'); socket.off('error'); socket.off('close'); }); return { socket, send: socket.send.bind(socket), on: socket.on.bind(socket), off: socket.off.bind(socket) }; }
在组件中使用:
import { defineComponent } from 'vue'; import { useSocket } from './useSocket'; export default defineComponent({ name: 'YourComponent', setup() { const { socket, send, on, off } = useSocket('ws://your-websocket-url'); on('open', event => { console.log("Connected to server", event); }); on('message', data => { console.log("Received data:", data); }); on('error', error => { console.error("WebSocket Error:", error); }); on('close', event => { console.log("Connection closed", event); }); return { send }; } });
三、总结
以上是具体实现方案,在后续开发过程中如果有更好的写法,也会更新本文。