Vue 3 + TypeScript 封装 @stomp/stompjs:实现自动重连与 URL 热切换的 WebSocket 客户端
1. 引言
在现代前端应用中,实时通信已成为不可或缺的功能。STOMP(Simple Text Oriented Messaging Protocol)作为 WebSocket 的子协议,为消息传递提供了清晰的帧格式和语义。@stomp/stompjs 是一个优秀的 STOMP 客户端库,但在实际 Vue 3 项目中直接使用仍显繁琐。
本文将介绍一个基于 @stomp/stompjs 的 Vue 3 Composition API 封装,它解决了以下痛点:
- URL 热切换:当 WebSocket 地址变更时自动断开旧连接并建立新连接
- 自动重订阅:连接地址变更后自动恢复所有之前的订阅
- 响应式状态管理:连接状态可被 Vue 组件监听
- 模块级单例:避免重复创建客户端实例
2. 核心设计思路
2.1 模块级单例模式
封装采用模块级单例设计,确保整个应用只有一个 STOMP 客户端实例。这避免了资源浪费和状态不一致问题。
// ---- 模块级单例 ----
let stompClient: Client | null = null;
let currentBrokerUrl: string | null = null;
let connectPromise: Promise<void> | null = null;
2.2 响应式连接状态
使用 Vue 的 ref 管理连接状态,便于组件监听状态变化:
/** 连接状态 */
type ConnectionStatus = "disconnected" | "connecting" | "connected";
/** 响应式连接状态 */
const connectionStatus = ref<ConnectionStatus>("disconnected");
2.3 订阅管理
维护两个映射表来管理订阅:
subscriptionMap:当前活跃的订阅subscriptionCallbacks:保存的订阅回调(用于 URL 变更后重订阅)
3. 核心实现解析
3.1 客户端获取与 URL 热切换
getClient() 函数是封装的核心,负责处理 URL 变更:
function getClient(): Client {
const wsStore = useWebsocketStore();
const newUrl = wsStore.wsUrlNew;
// URL 没变且 Client 已存在,直接复用
if (stompClient && currentBrokerUrl === newUrl) {
return stompClient;
}
// URL 变了或首次创建 → 销毁旧 Client,建新的
if (stompClient) {
console.log(`[StompClient] URL 变更: ${currentBrokerUrl} → ${newUrl}`);
try {
stompClient.deactivate();
} catch {
// 忽略
}
stompClient = null;
connectionStatus.value = "disconnected";
connectPromise = null;
}
currentBrokerUrl = newUrl;
const hadSubscriptions = subscriptionCallbacks.size > 0;
stompClient = new Client({
brokerURL: newUrl,
reconnectDelay: 0,
// ... 其他配置
});
return stompClient;
}
3.2 自动重订阅机制
在 onConnect 回调中实现自动重订阅:
onConnect: () => {
connectionStatus.value = "connected";
console.log(`[StompClient] 已连接 → ${newUrl}`);
onConnectCallback?.();
// URL 变更后自动重订阅之前的 topic
if (hadSubscriptions) {
const topics = [...subscriptionCallbacks.keys()];
subscriptionMap.clear();
topics.forEach((topic) => {
const cb = subscriptionCallbacks.get(topic);
if (cb) {
const sub = stompClient!.subscribe(topic, (message) => {
let body: any = message.body;
try {
body = JSON.parse(message.body);
} catch { }
cb(body, message.headers as Record<string, string>);
});
subscriptionMap.set(topic, sub);
console.log(`[StompClient] 自动重订阅: ${topic}`);
}
});
}
},
3.3 连接管理
connect() 函数实现幂等连接,确保同一时间只有一个连接过程:
async function connect(): Promise<void> {
// 必须在状态检查前调用 getClient(),以检测 URL 变更
const client = getClient();
if (connectionStatus.value === "connected") return Promise.resolve();
if (connectPromise) return connectPromise;
connectionStatus.value = "connecting";
connectPromise = new Promise<void>((resolve, reject) => {
// ... 连接逻辑
});
return connectPromise;
}
4. 完整 API 使用示例
4.1 基本用法
import { useStompClient } from '@/utils/stomp-client';
// 在组件中
const { connect, subscribe, send, disconnect, connectionStatus } = useStompClient({
onConnect: () => console.log("连接成功"),
onDisconnect: () => console.log("连接断开"),
});
// 连接
await connect();
// 订阅主题
const subscription = subscribe("/topic/notifications", (body, headers) => {
console.log("收到消息:", body);
console.log("消息头:", headers);
});
// 发送消息
send("/app/chat", { text: "Hello World" });
// 取消订阅
subscription.unsubscribe();
// 断开连接
await disconnect();
4.2 在 Vue 组件中使用
<template>
<div>
<div>连接状态: {{ statusText }}</div>
<button @click="toggleConnection" :disabled="isConnecting">
{{ isConnected ? '断开连接' : '连接' }}
</button>
<div v-if="messages.length">
<h3>收到的消息:</h3>
<ul>
<li v-for="(msg, index) in messages" :key="index">{{ msg }}</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useStompClient } from '@/utils/stomp-client';
const messages = ref<string[]>([]);
const { connect, disconnect, subscribe, connectionStatus } = useStompClient();
const isConnected = computed(() => connectionStatus.value === 'connected');
const isConnecting = computed(() => connectionStatus.value === 'connecting');
const statusText = computed(() => {
switch (connectionStatus.value) {
case 'connected': return '已连接';
case 'connecting': return '连接中';
default: return '未连接';
}
});
// 连接/断开连接
const toggleConnection = async () => {
if (isConnected.value) {
await disconnect();
messages.value = [];
} else {
await connect();
subscribe('/topic/messages', (body) => {
messages.value.push(body.content);
});
}
};
</script>
4.3 API 函数详细说明
useStompClient(options?: { onConnect?: () => void; onDisconnect?: () => void })
创建 STOMP 客户端实例的 Composition API hook,支持配置连接/断开回调。
参数说明:
interface StompClientOptions {
// 连接成功时的回调函数
onConnect?: () => void;
// 连接断开时的回调函数
onDisconnect?: () => void;
}
使用示例:
// 1. 基本用法(无参数)
const { connect, disconnect } = useStompClient();
// 2. 带连接/断开回调
const { connect, disconnect } = useStompClient({
onConnect: () => {
console.log('STOMP 连接成功');
// 可以在这里执行连接成功后的初始化操作
showNotification('WebSocket 已连接', 'success');
},
onDisconnect: () => {
console.log('STOMP 连接断开');
// 可以在这里执行连接断开后的清理操作
showNotification('WebSocket 已断开', 'warning');
}
});
// 3. 在组件中使用
import { useStompClient } from '@/utils/stomp-client';
import { onUnmounted } from 'vue';
export default {
setup() {
// 创建客户端实例
const stompClient = useStompClient({
onConnect: () => {
console.log('组件内连接成功');
// 连接成功后自动订阅
stompClient.subscribe('/topic/updates', handleUpdates);
},
onDisconnect: () => {
console.log('组件内连接断开');
// 清理组件相关状态
}
});
// 组件卸载时断开连接
onUnmounted(async () => {
if (stompClient.isConnected()) {
await stompClient.disconnect();
}
});
return { stompClient };
}
};
注意事项:
onConnect和onDisconnect回调是全局的,所有使用同一个 hook 实例的地方都会触发- 回调函数会在对应的连接状态变化时同步执行
- 建议在需要处理连接状态变化的组件中配置回调
- 回调函数中避免执行耗时操作,以免阻塞连接流程
useStompClient() 返回一个包含以下函数和属性的对象:
connectionStatus: Ref<ConnectionStatus>
响应式连接状态引用,可用于在 Vue 组件中实时监听连接状态变化。
// 在组件中使用
const { connectionStatus } = useStompClient();
// 监听状态变化
watch(connectionStatus, (newStatus) => {
console.log('连接状态变化:', newStatus);
});
// 计算属性
const isConnected = computed(() => connectionStatus.value === 'connected');
const isConnecting = computed(() => connectionStatus.value === 'connecting');
const isDisconnected = computed(() => connectionStatus.value === 'disconnected');
connect(): Promise<void>
建立 STOMP 连接,支持幂等调用(多次调用不会重复连接)。
const { connect } = useStompClient();
// 基本用法
await connect();
// 错误处理
try {
await connect();
console.log('连接成功');
} catch (error) {
console.error('连接失败:', error);
}
// 幂等性:多次调用只建立一次连接
await connect(); // 第一次调用,建立连接
await connect(); // 第二次调用,直接返回(不会重复连接)
disconnect(): Promise<void>
断开 STOMP 连接,清理所有订阅。
const { disconnect } = useStompClient();
// 基本用法
await disconnect();
// 配合连接状态使用
const { connect, disconnect, connectionStatus } = useStompClient();
const toggleConnection = async () => {
if (connectionStatus.value === 'connected') {
await disconnect();
console.log('已断开连接');
} else {
await connect();
console.log('已连接');
}
};
// 断开连接时会自动清理所有订阅
await disconnect();
// 此时 subscriptionMap 和 subscriptionCallbacks 都会被清空
subscribe(topic: string, callback: MessageCallback): StompSubscription
订阅指定主题,返回订阅对象用于后续取消订阅。
const { subscribe } = useStompClient();
// 基本用法
const subscription = subscribe('/topic/notifications', (body, headers) => {
console.log('收到消息:', body);
console.log('消息头:', headers);
});
// 处理 JSON 消息
subscribe('/topic/data', (body) => {
// body 已自动尝试 JSON.parse,如果失败则保持原字符串
if (typeof body === 'object') {
console.log('JSON 数据:', body.id, body.content);
} else {
console.log('文本数据:', body);
}
});
// 订阅多个主题
const sub1 = subscribe('/topic/user/123', handleUserMessages);
const sub2 = subscribe('/topic/system', handleSystemMessages);
unsubscribe(topic: string): void
取消订阅指定主题。
const { subscribe, unsubscribe } = useStompClient();
// 方式1:使用 subscribe 返回的 subscription 对象
const subscription = subscribe('/topic/test', callback);
subscription.unsubscribe(); // 取消订阅
// 方式2:直接使用 unsubscribe 函数
subscribe('/topic/test', callback);
unsubscribe('/topic/test'); // 取消订阅
// 批量取消订阅
const topics = ['/topic/a', '/topic/b', '/topic/c'];
topics.forEach(topic => subscribe(topic, callback));
// 取消所有订阅
topics.forEach(topic => unsubscribe(topic));
send(destination: string, body: any, headers?: Record<string, string>): void
向指定目的地发送消息。
const { send } = useStompClient();
// 发送简单消息
send('/app/chat', { text: 'Hello World' });
// 发送带 headers 的消息
send('/app/command',
{ command: 'start', params: { id: 123 } },
{ 'content-type': 'application/json', 'priority': 'high' }
);
// 发送字符串消息
send('/app/log', '这是一条日志消息');
// 发送前确保已连接
const { connect, send, connectionStatus } = useStompClient();
const sendMessage = async (destination: string, body: any) => {
if (connectionStatus.value !== 'connected') {
await connect();
}
send(destination, body);
};
isConnected(): boolean
检查当前是否已连接(同步函数)。
const { isConnected, connect } = useStompClient();
// 同步检查连接状态
if (isConnected()) {
console.log('当前已连接');
send('/app/ping', { time: Date.now() });
} else {
console.log('当前未连接');
await connect();
}
// 与响应式 connectionStatus 的区别
const { isConnected, connectionStatus } = useStompClient();
// isConnected() 是同步函数,返回当前布尔值
console.log(isConnected()); // true/false
// connectionStatus 是响应式引用,适合模板中使用
// 模板中:<div>状态: {{ connectionStatus }}</div>
isSubscribed(topic: string): boolean
检查是否已订阅指定主题。
const { subscribe, isSubscribed } = useStompClient();
// 订阅前检查
if (!isSubscribed('/topic/important')) {
subscribe('/topic/important', handleImportantMessages);
}
// 避免重复订阅
const topic = '/topic/updates';
if (!isSubscribed(topic)) {
subscribe(topic, handleUpdates);
}
// 配合 unsubscribe 使用
const toggleSubscription = (topic: string, callback: MessageCallback) => {
if (isSubscribed(topic)) {
unsubscribe(topic);
console.log(`已取消订阅 ${topic}`);
} else {
subscribe(topic, callback);
console.log(`已订阅 ${topic}`);
}
};
完整类型定义
export function useStompClient(options?: {
onConnect?: () => void;
onDisconnect?: () => void;
}): {
// 响应式状态
connectionStatus: Ref<ConnectionStatus>;
// 连接管理
connect: () => Promise<void>;
disconnect: () => Promise<void>;
// 订阅管理
subscribe: (topic: string, callback: MessageCallback) => StompSubscription;
unsubscribe: (topic: string) => void;
// 消息发送
send: (destination: string, body: any, headers?: Record<string, string>) => void;
// 状态检查
isConnected: () => boolean;
isSubscribed: (topic: string) => boolean;
};
5. 与 Pinia 状态管理集成
5.1 WebSocket Store 设计
// stores/websocket.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useWebsocketStore = defineStore('websocket', () => {
// 连接指定ip
const wsUrlNew = ref<string>('');
const setWsUrlNew = (url: string) => {
wsUrlNew.value = url;
console.log('[WebSocket Store] 地址已更新:', url.length > 0 ? url : '地址为空');
};
return {
wsUrlNew,
setWsUrlNew,
};
});
5.2 URL 变更触发重连
当 Pinia store 中的 wsUrlNew 发生变化时,下次调用 connect() 会自动:
- 检测到 URL 变更
- 断开旧连接
- 建立新连接
- 自动重订阅所有之前的主题
6. 错误处理与调试
6.1 错误处理
try {
await connect();
} catch (error) {
console.error('连接失败:', error);
// 显示用户友好的错误信息
showErrorMessage(`WebSocket 连接失败: ${error.message}`);
}
// 配置错误回调
const { connect, subscribe } = useStompClient({
onConnect: () => console.log('连接成功'),
onDisconnect: () => console.log('连接断开'),
// 可以扩展支持 onError 回调
});
6.2 调试日志
封装内置了详细的调试日志,可通过环境变量控制:
const DEBUG = import.meta.env.DEV;
// 在关键位置添加条件日志
if (DEBUG) {
console.log(`[StompClient] URL 变更: ${currentBrokerUrl} → ${newUrl}`);
console.log(`[StompClient] 自动重订阅: ${topic}`);
}
7. 性能优化建议
7.1 防抖连接
避免频繁调用 connect():
import { debounce } from 'lodash-es';
const debouncedConnect = debounce(async () => {
try {
await connect();
} catch (error) {
console.error('连接失败:', error);
}
}, 1000);
// 在 URL 变化时调用
watch(() => wsStore.wsUrlNew, debouncedConnect);
7.2 心跳检测
配置心跳机制保持连接活跃:
stompClient = new Client({
brokerURL: newUrl,
reconnectDelay: 5000, // 5秒重连延迟
heartbeatIncoming: 10000, // 10秒接收心跳
heartbeatOutgoing: 10000, // 10秒发送心跳
// ... 其他配置
});
8. 总结
本文介绍的 STOMP 客户端封装具有以下优势:
- URL 热切换:无需手动处理连接重建,地址变更自动处理
- 状态保持:连接断开后自动恢复所有订阅
- 响应式集成:完美融入 Vue 3 响应式系统
- 类型安全:完整的 TypeScript 类型支持
- 易于扩展:模块化设计便于添加新功能
这个封装特别适合需要动态切换 WebSocket 服务器地址的应用场景,如多租户系统、环境切换、故障转移等场景。
9. 完整代码
以下是完整的 STOMP 客户端封装代码,包含所有核心功能和与 Pinia 的集成:
9.1 STOMP 客户端封装 (stomp-client.ts)
// src/utils/stomp-client.ts
import { Client, IMessage, StompSubscription } from '@stomp/stompjs';
import { ref, Ref } from 'vue';
import { useWebsocketStore } from '@/stores/websocket';
/** 连接状态类型 */
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected';
/** 消息回调函数类型 */
type MessageCallback = (body: any, headers: Record<string, string>) => void;
// ---- 模块级单例 ----
let stompClient: Client | null = null;
let currentBrokerUrl: string | null = null;
let connectPromise: Promise<void> | null = null;
// ---- 响应式状态 ----
/** 响应式连接状态 */
const connectionStatus = ref<ConnectionStatus>('disconnected');
// ---- 订阅管理 ----
/** 当前活跃的订阅映射 */
const subscriptionMap = new Map<string, StompSubscription>();
/** 保存的订阅回调(用于 URL 变更后重订阅) */
const subscriptionCallbacks = new Map<string, MessageCallback>();
// ---- 回调函数 ----
let onConnectCallback: (() => void) | null = null;
let onDisconnectCallback: (() => void) | null = null;
/**
* 获取 STOMP 客户端实例(支持 URL 热切换)
*/
function getClient(): Client {
const wsStore = useWebsocketStore();
const newUrl = wsStore.wsUrlNew;
// URL 没变且 Client 已存在,直接复用
if (stompClient && currentBrokerUrl === newUrl) {
return stompClient;
}
// URL 变了或首次创建 → 销毁旧 Client,建新的
if (stompClient) {
console.log(`[StompClient] URL 变更: ${currentBrokerUrl} → ${newUrl}`);
try {
stompClient.deactivate();
} catch {
// 忽略
}
stompClient = null;
connectionStatus.value = 'disconnected';
connectPromise = null;
}
currentBrokerUrl = newUrl;
const hadSubscriptions = subscriptionCallbacks.size > 0;
stompClient = new Client({
brokerURL: newUrl,
reconnectDelay: 0,
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
debug: (str) => {
if (import.meta.env.DEV) {
console.log('[STOMP]', str);
}
},
onConnect: () => {
connectionStatus.value = 'connected';
console.log(`[StompClient] 已连接 → ${newUrl}`);
onConnectCallback?.();
// URL 变更后自动重订阅之前的 topic
if (hadSubscriptions) {
const topics = [...subscriptionCallbacks.keys()];
subscriptionMap.clear();
topics.forEach((topic) => {
const cb = subscriptionCallbacks.get(topic);
if (cb) {
const sub = stompClient!.subscribe(topic, (message: IMessage) => {
let body: any = message.body;
try {
body = JSON.parse(message.body);
} catch {
// 保持原字符串
}
cb(body, message.headers as Record<string, string>);
});
subscriptionMap.set(topic, sub);
console.log(`[StompClient] 自动重订阅: ${topic}`);
}
});
}
},
onDisconnect: () => {
connectionStatus.value = 'disconnected';
console.log('[StompClient] 连接断开');
onDisconnectCallback?.();
},
onStompError: (frame) => {
console.error('[StompClient] STOMP 错误:', frame.headers['message'], frame.body);
},
onWebSocketError: (event) => {
console.error('[StompClient] WebSocket 错误:', event);
connectionStatus.value = 'disconnected';
},
});
return stompClient;
}
/**
* 创建 STOMP 客户端实例的 Composition API hook
*/
export function useStompClient(options?: {
onConnect?: () => void;
onDisconnect?: () => void;
}) {
// 设置回调函数
if (options?.onConnect) {
onConnectCallback = options.onConnect;
}
if (options?.onDisconnect) {
onDisconnectCallback = options.onDisconnect;
}
/**
* 建立 STOMP 连接(幂等)
*/
async function connect(): Promise<void> {
// 必须在状态检查前调用 getClient(),以检测 URL 变更
const client = getClient();
if (connectionStatus.value === 'connected') return Promise.resolve();
if (connectPromise) return connectPromise;
connectionStatus.value = 'connecting';
connectPromise = new Promise<void>((resolve, reject) => {
const timeoutId = setTimeout(() => {
connectionStatus.value = 'disconnected';
connectPromise = null;
reject(new Error('连接超时'));
}, 10000);
client.onConnect = () => {
clearTimeout(timeoutId);
resolve();
};
client.onStompError = (frame) => {
clearTimeout(timeoutId);
connectionStatus.value = 'disconnected';
connectPromise = null;
reject(new Error(frame.headers['message'] || 'STOMP 连接错误'));
};
client.onWebSocketError = (event) => {
clearTimeout(timeoutId);
connectionStatus.value = 'disconnected';
connectPromise = null;
reject(new Error('WebSocket 连接错误'));
};
try {
client.activate();
} catch (error) {
clearTimeout(timeoutId);
connectionStatus.value = 'disconnected';
connectPromise = null;
reject(error);
}
});
return connectPromise;
}
/**
* 断开 STOMP 连接
*/
async function disconnect(): Promise<void> {
if (!stompClient || connectionStatus.value === 'disconnected') {
return Promise.resolve();
}
try {
stompClient.deactivate();
connectionStatus.value = 'disconnected';
subscriptionMap.clear();
subscriptionCallbacks.clear();
connectPromise = null;
console.log('[StompClient] 已断开连接');
} catch (error) {
console.error('[StompClient] 断开连接失败:', error);
throw error;
}
}
/**
* 订阅指定主题
*/
function subscribe(topic: string, callback: MessageCallback): StompSubscription {
const client = getClient();
if (subscriptionMap.has(topic)) {
console.warn(`[StompClient] 主题 ${topic} 已订阅,将取消旧订阅`);
unsubscribe(topic);
}
subscriptionCallbacks.set(topic, callback);
if (connectionStatus.value === 'connected') {
const subscription = client.subscribe(topic, (message: IMessage) => {
let body: any = message.body;
try {
body = JSON.parse(message.body);
} catch {
// 保持原字符串
}
callback(body, message.headers as Record<string, string>);
});
subscriptionMap.set(topic, subscription);
console.log(`[StompClient] 已订阅: ${topic}`);
return subscription;
} else {
// 未连接时,先保存回调,连接成功后会通过 onConnect 自动订阅
console.log(`[StompClient] 已保存订阅回调: ${topic}(等待连接)`);
return {
unsubscribe: () => unsubscribe(topic),
} as StompSubscription;
}
}
/**
* 取消订阅指定主题
*/
function unsubscribe(topic: string): void {
if (subscriptionMap.has(topic)) {
const subscription = subscriptionMap.get(topic);
subscription?.unsubscribe();
subscriptionMap.delete(topic);
console.log(`[StompClient] 已取消订阅: ${topic}`);
}
subscriptionCallbacks.delete(topic);
}
/**
* 向指定目的地发送消息
*/
function send(destination: string, body: any, headers?: Record<string, string>): void {
if (connectionStatus.value !== 'connected') {
throw new Error('未连接,无法发送消息');
}
const client = getClient();
const payload = typeof body === 'string' ? body : JSON.stringify(body);
client.publish({
destination,
body: payload,
headers: {
'content-type': 'application/json',
...headers,
},
});
}
/**
* 检查当前是否已连接
*/
function isConnected(): boolean {
return connectionStatus.value === 'connected';
}
/**
* 检查是否已订阅指定主题
*/
function isSubscribed(topic: string): boolean {
return subscriptionMap.has(topic);
}
return {
// 响应式状态
connectionStatus,
// 连接管理
connect,
disconnect,
// 订阅管理
subscribe,
unsubscribe,
// 消息发送
send,
// 状态检查
isConnected,
isSubscribed,
};
}
export type { ConnectionStatus, MessageCallback };
9.2 WebSocket Store (websocket.ts)
// src/stores/websocket.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useWebsocketStore = defineStore('websocket', () => {
// 连接指定ip
const wsUrlNew = ref<string>('');
const setWsUrlNew = (url: string) => {
wsUrlNew.value = url;
console.log('[WebSocket Store] 地址已更新:', url.length > 0 ? url : '地址为空');
};
return {
wsUrlNew,
setWsUrlNew,
};
});
9.3 安装依赖
npm install @stomp/stompjs
9.4 使用示例
// 在 Vue 组件中使用
import { useStompClient } from '@/utils/stomp-client';
import { useWebsocketStore } from '@/stores/websocket';
// 设置 WebSocket 地址
const wsStore = useWebsocketStore();
wsStore.setWsUrlNew('ws://localhost:8080/ws');
// 创建客户端
const { connect, subscribe, send, disconnect, connectionStatus } = useStompClient({
onConnect: () => console.log('连接成功'),
onDisconnect: () => console.log('连接断开'),
});
// 连接
await connect();
// 订阅
const subscription = subscribe('/topic/notifications', (body, headers) => {
console.log('收到通知:', body);
});
// 发送消息
send('/app/chat', { text: 'Hello World' });
// 断开连接
await disconnect();
9.5 扩展建议
根据项目需求,可以进一步扩展功能:
- 消息队列:在离线时缓存消息,连接恢复后重新发送
- 自动重连策略:实现指数退避重连机制
- 消息压缩:对大消息进行压缩传输
- 认证支持:集成 JWT、OAuth 等认证方式
- 连接池:支持多个 WebSocket 连接
- 消息持久化:将重要消息保存到本地存储
这个完整的封装提供了 URL 热切换、自动重订阅、响应式状态管理等核心功能,可以直接在项目中使用。
更多推荐

所有评论(0)