在vue3中封装使用WebSocket

上篇文章记录了如何在日常开发过程中引入并使用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.opts.maxReconnectAttempts) {
            setTimeout(() => {
                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.opts.maxReconnectAttempts!) {
      setTimeout(() => {
        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
    };
  }
});

三、总结

以上是具体实现方案,在后续开发过程中如果有更好的写法,也会更新本文。

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
Vue3 ,可以通过自定义一个 WebSocketVue 插件来封装 WebSocket 功能,具体步骤如下: 1. 创建一个 vue-websocket.js 文件,代码如下: ```javascript class WebSocketPlugin { constructor(url) { this.url = url; this.websocket = null; this.onMessageCallbacks = []; } connect() { this.websocket = new WebSocket(this.url); this.websocket.onopen = () => { console.log('WebSocket opened'); }; this.websocket.onclose = () => { console.log('WebSocket closed'); }; this.websocket.onmessage = (event) => { this.onMessageCallbacks.forEach((callback) => { callback(JSON.parse(event.data)); }); }; } send(data) { this.websocket.send(JSON.stringify(data)); } onMessage(callback) { this.onMessageCallbacks.push(callback); } } export default { install: (app, options) => { const ws = new WebSocketPlugin(options.url); app.config.globalProperties.$ws = ws; app.provide('ws', ws); ws.connect(); }, }; ``` 2. 在 main.js 注册该插件,代码如下: ```javascript import { createApp } from 'vue'; import App from './App.vue'; import WebSocketPlugin from './vue-websocket'; const app = createApp(App); app.use(WebSocketPlugin, { url: 'ws://localhost:8080' }); app.mount('#app'); ``` 3. 在 Vue 组件使用 WebSocket,代码如下: ```javascript <template> <div> <button @click="send">Send</button> </div> </template> <script> export default { mounted() { this.$ws.onMessage((data) => { console.log(data); }); }, methods: { send() { this.$ws.send({ message: 'Hello WebSocket' }); }, }, }; </script> ``` 以上是一个简单的 Vue3 封装 WebSocket 的示例代码,可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值