封装websocket,兼容uniapp及普通vue3应用

本文将实现websocket的封装,采用typescript+vue3实现,同时支持uniapp中使用。
主要实现两个部分:
websocket.core.ts,一个封装的websocket对象,用于处理websocket断线重连及生命周期钩子触发
useWebsocket.ts,采用vue hook封装上述对象,将实例属性转换为响应式属性以供页面组件内使用

websocket.core.ts

type Timeout = NODEJS.timers.Timeout;

export type Options = {
  reconnectLimit: number; // 最大重新连接次数
  reconnectInterval: number; // 重新连接的时间间隔
  onOpen?: () => void; // 连接打开时的回调
  onMessage?: (msg: string) => void; // 收到消息时的回调
  onClose?: () => void; // 连接关闭时的回调
  onError?: () => void; // 连接出错时的回调
  onReconnect?: (times: number) => void; // 重新连接的回调
  onReadStateChange?: (readState: WsReadyState) => void; // 连接状态变化时的回调
};

// 连接状态
export enum WsReadyState {
  CONNECTING = 0, // 等待连接
  OPEN = 1, // 连接中
  CLOSING = 2, // 关闭中
  CLOSED = 3, // 已关闭
  ERROR = 4, // 异常
}

export class Ws {
  private options: Options; // 参数
  private wsUrl: string; // 连接地址
  private client: WebSocket | null = null; // 连接实例
  private readyState: WsReadyState = WsReadyState.CONNECTING; // 连接状态
  private message: string | undefined; // 最后一次收到的消息
  private reConnectNum: number = 0; // 重新连接数
  private timer: Timeout | null = null; // 重新请求的timer

  /**
   * 构造方法
   * @param {string} url websocket 地址
   * @param {number} maxConnectNumber  最大重连次数
   */
  constructor(
    url: string,
    options: Options = { reconnectLimit: 10, reconnectInterval: 1000 }
  ) {
    this.wsUrl = url;
    this.options = options;
  }

  /**
   * 连接状态变化时的回调
   */
  public onReadStateChange() {
    this.options?.onReadStateChange &&
      this.options.onReadStateChange(this.readyState);
  }

  /**
   * 清除定时器
   */
  private clearTimer() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }

  public connect() {
    if (this.client == null) {
      // 手动打开时重置
      if (this.reConnectNum > this.options.reconnectLimit) {
        this.reConnectNum = 0;
      }
      this.readyState = WsReadyState.CONNECTING;

      const ws = new WebSocket(this.wsUrl);
      ws.onopen = this.onOpen.bind(this);
      ws.onerror = this.onError.bind(this);
      ws.onclose = this.onClose.bind(this);
      ws.onmessage = this.onMessage.bind(this);

      // const socketTask: any = uni.connectSocket({
      //     url: this.wsUrl,
      //     timeout: 1000,
      //     complete: () => { },
      // });
      // socketTask.onOpen(this.onOpen.bind(this));
      // socketTask.onError(this.onError.bind(this));
      // socketTask.onClose(this.onClose.bind(this));
      // socketTask?.onMessage(this.onMessage.bind(this));
      this.onReadStateChange();
      this.client = ws;
    }
  }

  private onOpen() {
    this.readyState = WsReadyState.OPEN;
    this.onReadStateChange();
    // 连接成功重置重连次数
    this.reConnectNum = 0;
    this.options?.onOpen && this.options.onOpen();
  }

  private async onError() {
    this.readyState = WsReadyState.ERROR;
    this.client = null;
    this.onReadStateChange();
    this.options?.onError && this.options.onError();
    // 重新尝试连接
    await this.reconnect();
  }

  private async onClose() {
    this.readyState = WsReadyState.CLOSING;
    this.readyState = WsReadyState.CLOSED;
    this.onReadStateChange();
    this.options?.onClose && this.options.onClose();
    this.client = null;
  }

  private onMessage(res: { data: string }) {
    const msg = res.data;
    this.message = msg;
    this.options?.onMessage && this.options.onMessage(msg);
  }

  private async reconnect() {
    this.clearTimer();
    this.timer = setTimeout(() => {
      if (this.readyState === WsReadyState.OPEN || this.client) {
        this.clearTimer();
        return;
      }
      this.reConnectNum++;
      if (this.reConnectNum <= this.options.reconnectLimit) {
        console.log(
          "第",
          this.reConnectNum,
          "次连接。最大连接次数:",
          this.options.reconnectLimit
        );
        this.connect();
        this.options?.onReconnect &&
          this.options.onReconnect(this.reConnectNum);
      }
    }, this.options.reconnectInterval);
  }

  /**
   * @description: 获取连接实例
   * @return {WebSocket | null}
   */
  public getClient(): WebSocket | null {
    return this.client;
  }

  /**
   * @description: 获取连接状态
   * @return {WsReadyState}
   */
  public getReadyState(): WsReadyState {
    return this.readyState;
  }

  /**
   * 关闭连接
   */
  public disconnect() {
    this.reConnectNum = this.options.reconnectLimit + 1;
    this.clearTimer();
    this.client?.close();
  }

  /**
   * @description: 发送websocket消息
   * @param {string} message
   * @return {*}
   */
  public sendMessage(message: string): boolean {
    if (this.client == null) return false;
    try {
      this.client.send(message);
      // this.client.send({
      //     data: message
      // });
      return true;
    } catch {
      return false;
    }
  }

  /**
   * 获取收到的最新消息
   * @returns {string | undefined}
   */
  public getMessage(): string | undefined {
    return this.message;
  }
}

将connect方法中注释内容替换其上方ws相关内容,即可转为uniapp可使用对象。

实现useWebsocket.ts

import { ref, type Ref, watchEffect, onBeforeUnmount } from "vue";
import { WsReadyState, Ws } from "./websocket.utils";
import type { Options as WsOptions } from "./websocket.utils";
import { defaults } from "lodash";

export type Options = WsOptions & {
    manual?: boolean; // 是否手动开启连接
    ready?: Ref<boolean>; // ready不为false时开启连接
    onOpen?: () => void;
}

const defaultOptions: Options = {
    reconnectLimit: 10,
    reconnectInterval: 1000,
    manual: false
}

export function useWebsocket(url: string, options: Partial<Options> = {}) {
    const targetOptions: Options = defaults(options, defaultOptions);
    let ws: Ws | undefined;
    const message = ref<string>();
    const readState = ref<WsReadyState>(WsReadyState.CONNECTING);

    const init = () => {
        if (ws) return;
        ws = new Ws(url, {
            ...targetOptions,
            onMessage: (value: string) => {
                message.value = value;
                targetOptions.onMessage && targetOptions.onMessage(value);
            },
            onReadStateChange: (value: WsReadyState) => {
                readState.value = value;
                targetOptions?.onReadStateChange && targetOptions.onReadStateChange(value);
            },
            onOpen() {
                options.onOpen && options.onOpen();
            }
        });
    }

    init();

    const connect = () => {
        ws?.connect();
    }
    const disconnect = () => {
        if (readState.value === WsReadyState.OPEN) {
            ws?.disconnect();
        }
    }

    const sendMessage = (message: any) => {
        ws?.sendMessage(message);
    }

    if (targetOptions.ready) {
        watchEffect(() => {
            if (targetOptions.ready?.value) {
                if (ws) ws.disconnect();
                readState.value = WsReadyState.CONNECTING;
                connect();
            } else {
                disconnect();
            }
        });
    }

    if (!(targetOptions.manual === true)) {
        connect();
    }

    onBeforeUnmount(disconnect);

    return {
        readState,
        message,
        connect,
        disconnect,
        sendMessage
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值