Vue3.3 项目开发中常用的通用hooks(不定期更新中……)

250 阅读3分钟

useVModel

组件绑定一个v-model属性。 注意:在Vue3.3版本中,简化了用法,支持 defineModel 直接声明,自动注册一个 prop,并返回一个可以直接变更的 ref

源码

/**
 * @description 组件绑定一个v-model
 * @param {Object} props 组件的props
 * @param {propName} props 属性名称
 * @param {Function} props Vue的emit方法
 * @date 
 * @author 
 */
import { computed } from 'vue';

export function useVModel(props: object, propName: string, emit) {
  return computed({
    get: () => {
      if (typeof props[propName] === 'object' && props[propName] !== null) {
        const obj = {};
        return new Proxy(props[propName], {
          set(target, name, val) {
            let value
            if(Array.isArray(target)){
              // 当为数组时,name为数组的索引
              value = target
              value[name] = val
            } else {
              obj[name] = val; // vue更新原理只更新一次,所以当更改多个值时只会有一个值更新的bug,所以通过obj收集修改的属性,emit统一处理
              value = {
                ...target,
                ...obj,
                [name]: val,
              }
            }
            emit('update:' + propName, value);
            return true;
          },
        });
      } else {
        return props[propName];
      }
    },
    set: (val) => {
      emit('update:' + propName, val);
    },
  });
}

示例

定义一个组件Child,使用useVModel双向绑定
<script setup lang="ts">
    import { useVModel } from "m-ui/hooks";
    const props = defineProps({
      model: {
        type: Object,
        default: () => ({
          input: null,
          select: null,
        }),
      },
    });
    const emit = defineEmits(["update:model"]);
    const _model = useVModel(props, "model", emit);
  </script>

  <template>
    <ms-space>
      <ms-input v-model:value="_model.input" />
      <ms-select
        class="w-[200px]"
        v-model:value="_model.select"
        :options="[{ value: 'select', label: '下拉框' }]"
      />
    </ms-space>
  </template>
使用时通过v-model双向绑定
<script setup lang="ts">
import Child from "./Child.vue";
const model = ref({
  input: "你好",
  select: null,
});
const handleClick = () => {
  console.log(model.value);
};
</script>

<template>
  <ms-card>
    <Child v-model:model="model" />
  </ms-card>
  <ms-button class="mt-4" type="primary" @click="handleClick">
    打印model
  </ms-button>
</template>

useChannel

多窗口/标签页通信, 创建一个频道,广播通知。

源码

/**
 * useChannel-- 多窗口/标签页通信, 创建一个频道,广播通知
 * @param {String} name 频道key
 * @param {Function} onMessage (data) => void 接收消息后的回调
 * @return {Object} { channel: BroadcastChannel; postMessage: (message: any) => void }
 * @return {Object.channel} channel 实例对象
 * @return {Object.postMessage} 广播消息的方法
 * @return {Object.closeChannel} 关闭断开与频道的连接
 * @author bingao.liu
 */

export function useChannel(
  name: string,
  onMessage: (data: any) => void
): { channel: BroadcastChannel; postMessage: (message: any) => void; closeChannel: () => void } {
  // BroadcastChannel是一种Web API,允许在同一来源(同一协议、主机名和端口号)的不同浏览器上下文
  //(如标签页、iframe、Web Worker)之间进行消息传递。
  // 它提供了一种简便、可靠的方法来实现跨上下文的实时通信
  
  const channel = new BroadcastChannel(name);

  // 监听频道 message 事件,频道收到消息时触发 message 事件
  const handleMessage = (e: MessageEvent<any>) => {
    try {
      onMessage?.(e.data);
    } catch (error) {
      console.error(error);
    }
  };

  channel.addEventListener('message', handleMessage);

  // 关闭断开与频道的连接
  const closeChannel = () => {
     try {
        channel.removeEventListener('message', handleMessage);
        channel.close();
     } catch (error) {
       console.error(error);
     }
  };
  window.addEventListener('beforeunload', closeChannel);
 // 发送消息
  const postMessage = (message: any) => {
     try {
       channel.postMessage(message);
     } catch (error) {
       console.error(error);
     }
   }
  return {
    channel,
    postMessage,
    closeChannel,
  };
}

useWebSocket

建立一个WebSocket长连接

源码

/**
 * @description useWebSocket--创建一个WebSocket连接
 * @param {String} url 连接的url
 * @param {Function} onMessage (data) => void 接收消息后的回调
 * @date 
 * @author 
 */

export function useWebSocket(
  url: string,
  onMessage: Function
): { initWebSocket: () => void } {
  function initWebSocket() {
    const ws = new WebSocket(`ws://${url}`);
    ws.onopen = function () {
      console.log("socket连接成功");
    };
    // 监听socket错误信息
    ws.onerror = function () {
      console.log("连接错误");
    };
    ws.onmessage = function (msg) {
      console.log(msg.data);
      onMessage && onMessage(msg);
    };
    ws.onclose = function () {
      console.log("socket已经关闭");
      setTimeout(reconnectWebSocket, 3000);
    };
  }
  initWebSocket();
  //重新连接webSocket
  function reconnectWebSocket() {
    initWebSocket(); //重新连接
  }
  return { initWebSocket };
}

useScheduler

接口调度,控制请求并发数。

/**
 * @description useScheduler--接口调度,控制请求并发数
 * @param {Number} count 最大并发数
 * @date 12/01/2023
 * @author 
 */

export function useScheduler(count: number) {
  return new Scheduler(count);
}

class Scheduler {
  count: number;
  queue: Array<any>;
  run: Array<any>;
  constructor(count: number) {
    this.count = count;
    this.queue = [];
    this.run = [];
  }

  add(task: any) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      if (this.run.length < this.count) {
        this._runTask();
      }
    });
  }

  _runTask() {
    const { task, resolve, reject } = this.queue.shift();
    this.run.push(task);
    task()
      .then(
        (result: any) => {
          resolve(result);
        },
        (error: any) => {
          reject(error);
        }
      )
      .finally(() => {
        this.run.splice(this.run.indexOf(task), 1);
        if (this.queue.length > 0) {
          this._runTask();
        }
      });
  }
}