封装 WebSocket 工具类

一、入门版:组件内直接使用(适合小demo)

核心要点

  1. 组件生命周期联动:created初始化连接,beforeDestroy关闭连接,避免内存泄漏
  2. 发送消息前做状态判断:确保 WebSocket 处于OPEN状态,防止消息发送失败
  3. 极简实现:仅保留核心通信逻辑,适合快速验证功能

完整可运行代码

复制代码
<template>
  <div>
    <h2>WebSocket 基础通信示例</h2>
    <button @click="sendMessage">发送测试消息</button>
    <p>服务端返回:{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      socket: null, // 存储WebSocket实例
      message: ""   // 存储接收的消息
    };
  },
  created() {
    // 组件创建时初始化连接
    this.initWebSocket();
  },
  beforeDestroy() {
    // 组件销毁时关闭连接,防止内存泄漏
    this.socket && this.socket.close();
  },
methods: {
    initWebSocket() {
      // 替换为自己的WebSocket服务端地址
      this.socket = new WebSocket("ws://localhost:8080");
      // 连接成功回调
      this.socket.onopen = () => {
        console.log("WebSocket 连接成功");
      };
      // 接收消息回调
      this.socket.onmessage = (event) => {
        this.message = event.data;
      };
      // 连接关闭回调
      this.socket.onclose = () => {
        console.log("WebSocket 已关闭");
      };
      // 连接错误回调
      this.socket.onerror = (err) => {
        console.error("WebSocket 连接错误", err);
      };
    },
    sendMessage() {
      // 确认连接处于打开状态再发送消息
      if (this.socket && this.socket.readyState === WebSocket.OPEN) {
        this.socket.send("Hello Server");
      }
    }
  }
};
</script>

二、进阶版:工具类封装(心跳 + 断线重连,中大型项目必备)

小项目的快速实现方案无法满足中大型项目的复用和稳定性需求,这时候就需要将 WebSocket 封装为工具类,核心实现心跳检测自动断线重连,解决网络波动、服务端临时断开的问题。

第一步:封装 WebSocket 工具类

创建/utils/websocket.js,封装通用的 WebSocket 逻辑,支持自定义心跳间隔、重连间隔:

复制代码
class WS {
constructor(url, options = {}) {
    this.url = url; // 服务端地址
    this.ws = null; // WebSocket实例
    this.lockReconnect = false; // 重连锁,防止重复重连
    // 自定义心跳间隔,默认5000ms
    this.heartbeatInterval = options.heartbeatInterval || 5000;
    // 自定义重连间隔,默认3000ms
    this.reconnectInterval = options.reconnectInterval || 3000;
    this.heartbeatTimer = null; // 心跳定时器
    this.reconnectTimer = null; // 重连定时器
    this.onMessage = options.onMessage || function () {}; // 自定义收消息回调
    this.init(); // 初始化连接
  }

// 初始化WebSocket
  init() {
    this.ws = new WebSocket(this.url);
    this.ws.onopen = () => {
      console.log("WebSocket 连接成功");
      this.startHeartbeat(); // 连接成功后开启心跳
    };
    this.ws.onmessage = (event) => {
      this.onMessage(event.data); // 执行自定义收消息回调
      this.startHeartbeat(); // 收到消息后重置心跳,防止误判断线
    };
    this.ws.onclose = () => {
      console.log("WebSocket 已关闭");
      this.reconnect(); // 关闭后自动重连
    };
    this.ws.onerror = () => {
      console.error("WebSocket 出错");
      this.reconnect(); // 出错后自动重连
    };
  }

// 发送消息
  send(msg) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(msg);
    } else {
      console.warn("WebSocket 未连接,消息未发送");
    }
  }

// 心跳检测:定时发送ping,确认连接有效性
  startHeartbeat() {
    clearTimeout(this.heartbeatTimer);
    this.heartbeatTimer = setTimeout(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send("ping");
      }
    }, this.heartbeatInterval);
  }

// 自动重连:加锁防止多次重连
  reconnect() {
    if (this.lockReconnect) return;
    this.lockReconnect = true;
    clearTimeout(this.reconnectTimer);
    this.reconnectTimer = setTimeout(() => {
      console.log("尝试重连 WebSocket...");
      this.init();
      this.lockReconnect = false;
    }, this.reconnectInterval);
  }
}

exportdefault WS;

第二步:Vue 组件中使用工具类

在组件中引入封装好的工具类,简单配置即可实现带心跳和重连的 WebSocket 通信:

复制代码
<template>
  <div>
    <h3>WebSocket 聊天功能</h3>
    <input v-model="input" placeholder="输入消息发送..." />
    <button @click="send">发送</button>
    <ul>
      <li v-for="(msg, i) in messages" :key="i">{{ msg }}</li>
    </ul>
  </div>
</template>

<script>
// 引入封装的WebSocket工具类
import WS from"@/utils/websocket";
exportdefault {
  data() {
    return {
      ws: null,
      input: "",
      messages: [] // 存储聊天消息
    };
  },
  created() {
    // 初始化WebSocket,配置收消息回调
    this.ws = new WS("ws://localhost:8080", {
      onMessage: (msg) => {
        this.messages.push(msg);
      }
    });
  },
methods: {
    send() {
      if (this.input.trim()) {
        this.ws.send(this.input); // 调用工具类的send方法
        this.messages.push("我: " + this.input);
        this.input = "";
      }
    }
  }
};
</script>

核心亮点

  1. 心跳检测 :定时发送ping包,服务端返回pong(无需前端处理),确认连接有效性

  2. 断线重连:重连锁防止多次触发重连,网络恢复后自动恢复连接

  3. 高度复用:工具类可在任意组件中引入,一次封装多处使用,减少代码冗余

  4. 自定义配置:支持自定义心跳和重连间隔,适配不同项目需求

三、终极版:全局插件封装(Vue2/3 通用 + Pinia/Vuex,企业级开发)

对于大型企业级项目,需要更完善的全局管理能力:全局注入 WebSocket 实例消息类型分发离线消息缓存页面刷新消息不丢失全局连接状态管理

这套方案封装为 Vue 全局插件,Vue2/Vue3 通用,结合 Pinia(Vue2 可替换为 Vuex)管理全局状态,实现一站式的 WebSocket 解决方案。

前置准备

Vue3 项目需安装 Pinia:npm install pinia,并在main.js中注册;Vue2 项目可使用 Vuex,思路完全一致。

第一步:创建 Pinia Store 管理全局状态

创建/stores/wsStore.js,管理 WebSocket 的连接状态全局消息 ,支持按类型筛选消息,遵循 Pinia最小可变点原则,精准更新数据减少组件重渲染:

复制代码
import { defineStore } from"pinia";

/**
 * wsStore:管理全局WebSocket消息和连接状态
 * 支持:消息存储、连接状态管理、按类型分发消息(chat/notification/system等)
 */
exportconst useWsStore = defineStore("ws", {
state: () => ({
    messages: [], // 存储所有收到的消息,格式{ type: 'xxx', content: 'xxx' }
    status: "closed"// 连接状态:connected/closed/error
  }),
actions: {
    /**
     * 添加消息:默认类型为default,精准更新数组避免整体替换
     * @param {Object} msgObj - 消息对象
     */
    addMessage(msgObj) {
      if (!msgObj.type) msgObj.type = "default";
      this.messages.push(msgObj); // 最小化变更,提升性能
    },
    /**
     * 按类型筛选消息
     * @param {string} type - 消息类型
     * @returns {Array} 对应类型的消息列表
     */
    getMessagesByType(type) {
      returnthis.messages.filter(msg => msg.type === type);
    },
    /**
     * 设置连接状态
     * @param {string} status - 连接状态
     */
    setStatus(status) {
      this.status = status;
    },
    /**
     * 清空所有消息
     */
    clearMessages() {
      this.messages = [];
    },
    /**
     * 从localStorage恢复消息:页面刷新后不丢失
     */
    initFromStorage() {
      const cacheMsg = localStorage.getItem("wsMessages");
      if (cacheMsg) this.messages = JSON.parse(cacheMsg);
      const cacheStatus = localStorage.getItem("wsStatus");
      if (cacheStatus) this.status = cacheStatus;
    },
    /**
     * 缓存消息到localStorage:实时同步
     */
    cacheToStorage() {
      localStorage.setItem("wsMessages", JSON.stringify(this.messages));
      localStorage.setItem("wsStatus", this.status);
    }
  }
});

第二步:封装 WebSocket 全局插件

创建/plugins/websocket.js,封装为 Vue 插件,实现全局注入消息类型解析离线缓存心跳 + 重连,自动将消息同步到 Pinia:

复制代码
import { useWsStore } from"@/stores/wsStore";

/**
 * WSPlugin:Vue2/Vue3通用WebSocket全局插件
 * 核心功能:全局注入$ws、心跳检测、自动重连、消息类型分发、离线缓存
 */
class WSPlugin {
constructor(url, options = {}) {
    this.url = url;
    this.ws = null;
    this.lockReconnect = false;
    this.heartbeatInterval = options.heartbeatInterval || 5000;
    this.reconnectInterval = options.reconnectInterval || 3000;
    this.heartbeatTimer = null;
    this.reconnectTimer = null;
    this.store = useWsStore(); // 引入Pinia Store
    this.customMessageHandler = options.onMessage || null; // 自定义消息回调
    this.init();
  }

// 初始化WebSocket连接
  init() {
    this.ws = new WebSocket(this.url);
    this.ws.onopen = () => {
      console.log("WebSocket 已连接");
      this.store.setStatus("connected");
      this.store.cacheToStorage(); // 缓存连接状态
      this.startHeartbeat();
    };
    this.ws.onmessage = (event) => {
      // 心跳响应pong不做处理
      if (event.data === "pong") return;
      let msgObj;
      // 解析消息:默认JSON格式,解析失败则设为default类型
      try {
        msgObj = JSON.parse(event.data);
      } catch {
        msgObj = { type: "default", content: event.data };
      }
      // 同步消息到Pinia并缓存到本地
      this.store.addMessage(msgObj);
      this.store.cacheToStorage();
      // 执行自定义消息回调
      if (typeofthis.customMessageHandler === "function") {
        this.customMessageHandler(msgObj);
      }
      this.startHeartbeat(); // 重置心跳
    };
    this.ws.onclose = () => {
      console.log("WebSocket 已关闭");
      this.store.setStatus("closed");
      this.store.cacheToStorage();
      this.reconnect();
    };
    this.ws.onerror = (err) => {
      console.error("WebSocket 出错", err);
      this.store.setStatus("error");
      this.store.cacheToStorage();
      this.reconnect();
    };
  }

// 发送消息:支持字符串/对象,对象自动序列化为JSON
  send(msg) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      if (typeof msg === "object") msg = JSON.stringify(msg);
      this.ws.send(msg);
    } else {
      console.warn("WebSocket 未连接,消息未发送");
    }
  }

// 心跳检测
  startHeartbeat() {
    clearTimeout(this.heartbeatTimer);
    this.heartbeatTimer = setTimeout(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send("ping");
      }
    }, this.heartbeatInterval);
  }

// 自动重连
  reconnect() {
    if (this.lockReconnect) return;
    this.lockReconnect = true;
    clearTimeout(this.reconnectTimer);
    this.reconnectTimer = setTimeout(() => {
      console.log("尝试重连 WebSocket...");
      this.init();
      this.lockReconnect = false;
    }, this.reconnectInterval);
  }

// 手动关闭连接
  close() {
    clearTimeout(this.heartbeatTimer);
    clearTimeout(this.reconnectTimer);
    this.ws && this.ws.close();
  }
}

// Vue插件安装方法:Vue2/Vue3通用
exportdefault {
  install(app, options) {
    const wsInstance = new WSPlugin(options.url, options);
    // Vue3 全局挂载
    if (app.config) {
      app.config.globalProperties.$ws = wsInstance;
    } else {
      // Vue2 全局挂载
      app.prototype.$ws = wsInstance;
    }
  }
};

第三步:全局注册插件(main.js)

在 Vue 入口文件中注册 Pinia 和 WebSocket 插件,全局注入 $ws 实例:

复制代码
import { createApp } from"vue";
import { createPinia } from"pinia";
import App from"./App.vue";
// 引入WebSocket全局插件
import WSPlugin from"./plugins/websocket";

const app = createApp(App);
const pinia = createPinia();
app.use(pinia);

// 安装WebSocket插件,配置服务端地址和自定义参数
app.use(WSPlugin, {
url: "ws://localhost:8080", // 替换为自己的服务端地址
heartbeatInterval: 5000,
reconnectInterval: 3000,
// 全局自定义消息回调
onMessage: (msg) => {
    console.log("全局收到消息:", msg);
  }
});

app.mount("#app");

第四步:Vue 组件中使用全局插件

组件中无需再手动初始化 WebSocket,直接使用$ws发送消息,从 Pinia 中获取消息和连接状态,支持按类型筛选,页面刷新后消息自动恢复:

复制代码
<template>
  <div>
    <h3>全局WebSocket - 聊天&通知</h3>
    <!-- 聊天消息 -->
    <div class="chat">
      <h4>聊天消息</h4>
      <ul>
        <li v-for="(msg, i) in chatMessages" :key="i">{{ msg.content }}</li>
      </ul>
    </div>
    <!-- 系统通知 -->
    <div class="notification">
      <h4>系统通知</h4>
      <ul>
        <li v-for="(msg, i) in notifications" :key="i">{{ msg.content }}</li>
      </ul>
    </div>
    <!-- 发送消息 -->
    <input v-model="input" placeholder="输入聊天消息..." />
    <button @click="sendChat">发送聊天</button>
    <button @click="clearAll">清空所有消息</button>
    <!-- 连接状态 -->
    <p>当前连接状态:{{ status }}</p>
</div>
</template>

<script>
import { ref, computed, onMounted } from"vue";
import { useWsStore } from"@/stores/wsStore";

exportdefault {
  setup() {
    const wsStore = useWsStore();
    const input = ref("");

    // 页面加载时从localStorage恢复消息
    onMounted(() => {
      wsStore.initFromStorage();
    });

    // 按类型筛选消息:聊天消息
    const chatMessages = computed(() => wsStore.getMessagesByType("chat"));
    // 按类型筛选消息:系统通知
    const notifications = computed(() => wsStore.getMessagesByType("notification"));
    // 连接状态
    const status = computed(() => wsStore.status);

    // 发送聊天消息:带类型,服务端可按类型分发
    const sendChat = () => {
      if (!input.value.trim()) return;
      // 调用全局$ws发送消息
      wsStore.$root?.$ws.send({ type: "chat", content: input.value });
      // 本地添加消息
      wsStore.addMessage({ type: "chat", content: "我: " + input.value });
      input.value = "";
    };

    // 清空所有消息
    const clearAll = () => {
      wsStore.clearMessages();
      wsStore.cacheToStorage();
    };

    return {
      input,
      chatMessages,
      notifications,
      status,
      sendChat,
      clearAll
    };
  }
};
</script>

终极版核心优势

  1. Vue2/Vue3 通用:一套代码适配两个 Vue 版本,降低迁移成本

  2. 全局注入 :所有组件可直接使用$ws发送消息,无需重复引入

  3. 消息类型分发 :按type字段区分聊天、通知、系统广播,组件按需渲染

  4. 离线消息缓存 :消息和连接状态同步到localStorage,页面刷新不丢失

  5. 全局状态管理:Pinia 统一管理连接状态和消息,多组件数据同步

  6. 性能优化:遵循 Pinia 最小可变点原则,精准更新数据减少组件重渲染

  7. 高度可扩展:支持自定义消息回调、心跳 / 重连间隔,适配各种业务场景

四、实用优化 & 避坑指南

🔧 前端优化

  1. 鉴权处理:建立连接时携带 token,保证通信安全

    • 方式 1:拼接在 url 后:ws://localhost:8080?token=xxx

    • 方式 2:通过子协议 header 传递(适合复杂鉴权)

  2. 存储优化 :消息量大时,用IndexedDB替代localStorage,提升存储性能和容量

  3. 消息去重 :聊天应用可结合用户 ID + 消息唯一 ID,避免离线重连后重复接收消息

  4. 消息过期:添加消息过期机制,定期清理本地缓存,防止缓存无限增长

  5. 重连限制:可添加最大重连次数,避免网络彻底断开时无限重连消耗资源

🔧 服务端配合

  1. 心跳响应 :服务端收到ping包后,及时返回pong包,确认连接有效性

  2. 高并发优化 :配合消息队列 + 负载均衡,避免单点 WebSocket 服务压力过大

  3. 离线消息推送:服务端记录用户离线消息,重连后主动推送,保证消息不丢失

  4. 连接管理:服务端定期清理无效连接,释放服务器资源

⚠️ 常见坑点

  1. 重连锁缺失 :未加锁会导致多次触发重连,创建多个 WebSocket 实例,必须添加lockReconnect

  2. 组件销毁未关连接 :Vue 组件销毁时未关闭 WebSocket,会导致内存泄漏,需在beforeDestroy中处理

  3. 发送消息未判断状态 :连接未打开时发送消息会失败,必须判断readyState === WebSocket.OPEN

  4. Pinia/Vuex 数据更新不及时:避免直接修改状态,使用 actions 精准更新,遵循最小可变点原则

相关推荐
2501_918126912 小时前
学习所有6502写游戏控制器的语句
java·linux·网络·汇编·嵌入式硬件
南梦浅2 小时前
三层网络搭建(思科模拟器)
网络·计算机网络
txinyu的博客2 小时前
解析muduo源码之 TcpConnection.h & TcpConnection.cc
网络
小义_2 小时前
随笔 1(Linux)
linux·运维·服务器·网络·云原生·红帽
进击的雷神2 小时前
主办方过滤、展位号模糊提取、多层级官网爬取、缅文编码解码——缅甸塑料展爬虫四大技术难关攻克纪实
网络·爬虫·python
上海云盾-小余2 小时前
CC 攻击与 DDoS 联动防护:如何构建一体化流量清洗架构
网络·安全·游戏·架构·ddos
向往着的青绿色3 小时前
雷池(SafeLine)社区版免费部署教程|从环境检查到防护实操全流程
网络·计算机网络·nginx·网络安全·容器·网络攻击模型·信息与通信
Larry_Yanan3 小时前
Qt网络开发之基于 QWebEngine 实现简易内嵌浏览器
linux·开发语言·网络·c++·笔记·qt·学习
斌味代码3 小时前
el-popover跳转页面不隐藏,el-popover销毁
前端·javascript·vue.js