export type AutoReconnectOptions = boolean | {
maxRetries?: number
retryInterval?: number
onMaxRetriesReached?: Function
}
export enum ConnectionStatus {
Disconnected = 'DISCONNECTED',
Connected = 'CONNECTED',
Error = 'ERROR'
}
class SocketService {
private static instance: SocketService
private ws: WebSocket | null = null
private listeners: Record<string, Function[]> = {}
private autoReconnect: AutoReconnectOptions = true
private retries: number = 0
private connectionStatus: ConnectionStatus = ConnectionStatus.Disconnected
private constructor() {
this.connect()
}
public static getInstance(): SocketService {
if (!SocketService.instance) {
SocketService.instance = new SocketService()
}
return SocketService.instance
}
public setAutoReconnectOptions(options: AutoReconnectOptions) {
this.autoReconnect = options
}
public connect() {
this.ws = new WebSocket('ws://localhost:3000/ws')
this.ws.onopen = () => {
this.connectionStatus = ConnectionStatus.Connected
this.emit('connected', null)
}
this.ws.onerror = () => {
this.connectionStatus = ConnectionStatus.Error
this.emit('error', null)
}
this.ws.onclose = () => {
this.connectionStatus = ConnectionStatus.Disconnected
this.emit('disconnected', null)
if (this.shouldReconnect()) {
setTimeout(() => this.connect(), this.getRetryInterval())
}
}
this.ws.onmessage = (event) => {
this.emit('message', event.data)
}
}
private shouldReconnect(): boolean {
if (typeof this.autoReconnect === 'boolean') {
return this.autoReconnect
} else if (this.autoReconnect) {
const { maxRetries } = this.autoReconnect
if (maxRetries !== undefined) {
if (this.retries < maxRetries) {
this.retries++
return true
} else if (this.retries >= maxRetries) {
this.autoReconnect.onMaxRetriesReached && this.autoReconnect.onMaxRetriesReached()
return false
}
}
}
return false
}
private getRetryInterval(): number {
if (typeof this.autoReconnect === 'boolean') {
return 1000
} else if (this.autoReconnect && this.autoReconnect.retryInterval) {
return this.autoReconnect.retryInterval
}
return 1000
}
public send(data: any) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data))
} else {
console.error('WebSocket 连接未打开')
}
}
public close() {
if (this.ws) {
this.ws.close()
}
}
private emit(event: string, data: any) {
if (!this.listeners[event]) {
return
}
this.listeners[event].forEach((listener) => listener(data))
}
public on(event: string, listener: Function) {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event].push(listener)
if (event === 'connected' && this.connectionStatus === ConnectionStatus.Connected) {
listener()
}
}
public off(event: string, listener: Function) {
if (!this.listeners[event]) {
return
}
this.listeners[event] = this.listeners[event].filter((l) => l !== listener)
}
public getConnectionStatus(): ConnectionStatus {
return this.connectionStatus
}
public watchConnectionStatus(callback: (status: ConnectionStatus) => void) {
this.on('connected', () => callback(ConnectionStatus.Connected))
this.on('disconnected', () => callback(ConnectionStatus.Disconnected))
this.on('error', () => callback(ConnectionStatus.Error))
}
}
export default SocketService
在 Vue3 中使用:
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'
import SocketService from '@/lib/SocketService'
const socketService = SocketService.getInstance()
const connectionStatus = ref(socketService.getConnectionStatus())
socketService.watchConnectionStatus((status) => {
connectionStatus.value = status
})
socketService.setAutoReconnectOptions({
maxRetries: 4,
retryInterval: 2000,
onMaxRetriesReached: () => {
console.log('max retries reached')
}
})
const onConnected = () => {
console.log('connected')
}
socketService.on('connected', onConnected)
const onDisconnected = () => {
console.log('disconnected')
}
socketService.on('disconnected', onDisconnected)
const onMessage = (data: any) => {
console.log('message', data)
}
socketService.on('message', onMessage)
onBeforeUnmount(() => {
socketService.off('connected', onConnected)
socketService.off('disconnected', onDisconnected)
socketService.off('message', onMessage)
})
</script>
<template>
<div>
<div>Connection status: {{ connectionStatus }}</div>
<button @click="socketService.connect">Connect</button>
<button @click="socketService.close">Close</button>
<button @click="socketService.send('Hello')">Send</button>
</div>
</template>
可以在不同的 Vue 组件/页面中通过 SocketService.getInstance() 获取同一个 WebSocket 连接,监听数据接收以及发送消息。