Vue 3 客户端 WebSocket 封装:自动重连、心跳与全局状态同步

test12026-10-2725 次阅读

为什么需要一层封装

原生 WebSocket 在弱网、刷新、后台切回前台时经常断开,业务里直接 new WebSocket 会导致到处散落重连逻辑。把连接、心跳、重连、消息分发统一抽到 composable,组件只关心"收到什么数据、要发什么"。

核心 composable

// useWebSocket.ts
import { ref, onUnmounted } from "vue";

export function useWebSocket(url: string) {
  const socket = ref<WebSocket | null>(null);
  const connected = ref(false);
  const messages = ref<any[]>([]);
  let heartbeat: number | undefined;
  let retry = 0;

  function connect() {
    const ws = new WebSocket(url);
    socket.value = ws;

    ws.onopen = () => {
      connected.value = true;
      retry = 0;
      heartbeat = window.setInterval(
        () => ws.send(JSON.stringify({ type: "ping" })),
        15000
      );
    };

    ws.onmessage = (e) => {
      const data = JSON.parse(e.data);
      if (data.type !== "pong") messages.value.push(data);
    };

    ws.onclose = () => {
      connected.value = false;
      window.clearInterval(heartbeat);
      if (retry < 5) {
        retry++;
        setTimeout(connect, Math.min(1000 * retry, 5000));
      }
    };
  }

  function send(payload: unknown) {
    if (socket.value?.readyState === WebSocket.OPEN) {
      socket.value.send(JSON.stringify(payload));
    }
  }

  connect();
  onUnmounted(() => {
    window.clearInterval(heartbeat);
    socket.value?.close();
  });

  return { connected, messages, send };
}

在组件里消费

// ChatRoom.vue
<script setup lang="ts">
import { useWebSocket } from "./useWebSocket";

const { connected, messages, send } = useWebSocket("wss://api.example.com/ws");

function say(text: string) {
  send({ type: "chat", text });
}
</script>

工程注意点

  • 心跳间隔要小于服务端超时阈值,一般 15-30 秒一次 ping。
  • 重连用指数退避,避免服务端恢复瞬间被大量连接打爆。
  • 组件卸载时一定清掉定时器并关闭连接,否则会泄漏 socket。
  • 跨标签页共享一份连接时,把 composable 提到应用根部或用 provide/inject 复用单例。
T

test1

文章作者

为什么需要一层封装 原生 WebSocket 在弱网、刷新、后台切回前台时经常断开,业务里直接 new WebSock...

分类
技术
发布时间
2026-10-27
字数
约 1785 字
阅读
25 次