Vue3+TS二次封装ant-design-vue4.x的Spin组件,实现全局加载形式(以服务的方式来调用)

写在开头

最近接手一个 Vue3 的项目,在做新业务需求的时候,发现项目中没有全局的 Loading 组件可以直接调用,Em......好像有点不太方便呀。😪

由于项目使用 ant-design-vue4.x 作为 UI 框架,它的 Spin 组件并不支持直接全局式的调用,小编期待的形式是它能和 element-plus 这种服务的方式来调用一样。😃

以服务的方式来调用

Loading 还可以以服务的方式调用。 你可以像这样引入 Loading 服务:

javascript 复制代码
import { ElLoading } from 'element-plus'

在你需要的时候通过下面的方式调用:

javascript 复制代码
ElLoading.service(options)

其中options参数为 Loading 的配置项,具体见下表。 LoadingService 会返回一个 Loading 实例,可通过调用该实例的 close 方法来关闭它:

javascript 复制代码
const loadingInstance = ElLoading.service(options)
nextTick(() => {
  // Loading should be closed asynchronously
  loadingInstance.close()
})

完全通过 JS 动态生成、动态插入、再动态删除,这能省下很多事情。

没办法,既然不支持,那就自己来封装一个呗,话不多说,直接开整。

正文

新建 Spin/index.ts 文件:

javascript 复制代码
import { defineComponent, createApp, h } from "vue";
import type { SpinConfig, SpinInstance } from "./types.ts";
import { Spin } from "ant-design-vue";

/** @name 单例 **/
let singleInstance: SpinInstance | undefined = undefined;

/** @name 创建Spin组件 **/
export function createSpinComponent(options: SpinConfig) {
  // 定义Spin组件的配置,defineComponent函数仅是在定义Vue组件时提供类型推导的辅助函数。
  const spinComponent = defineComponent({
    name: "Spin",
    setup() {
      // setup函数可以返回一个渲染函数
      return () => h('div', null, h(Spin, { ...options }));
    },
  });
  // 创建组件实例
  const spinInstance = createApp(spinComponent);
  // 将组件实例挂载在一个容器元素中,参数可以是一个实际的DOM元素或一个CSS选择器
  // 如果该组件有模板或定义了渲染函数,它将替换容器内所有现存的DOM节点。
  const vm = spinInstance.mount(document.createElement("div"));

  function close() {
    // 移除文档流的DOM节点
    vm.$el?.parentNode?.removeChild(vm.$el);
    // 销毁组件实例
    spinInstance.unmount();
    // 单例销毁
    singleInstance = undefined;
  }

  return {
    close,
    get $el(): HTMLElement {
      return vm.$el;
    },
  };
}

/** @name 入口函数 **/
function service(options: SpinConfig = {}): SpinInstance {
  if (singleInstance) return singleInstance;
  const resolved = resolveOptions(options);
  const intance = createSpinComponent({
    ...resolved
  });
  // Spin组件样式处理
  addStyle(instance);
  // 将Spin组件插入文档流中
  (resolved.target! as HTMLElement).appendChild(instance.$el);
  singleInstance = intance
  return intance;
}

/** @name 配置预处理,主要处理额外扩展的target属性,target不传,则默认使用body作为挂载节点 **/
const resolveOptions = (options: SpinConfig): SpinConfig => {
  let target: HTMLElement;
  const isString = (val: unknown): val is string => typeof val === "string";
  if (isString(options.target)) {
    target = document.querySelector<HTMLElement>(options.target) ?? document.body;
  } else {
    target = options.target || document.body;
  }
  return {
    ...options,
    target
  };
};

/** @name 处理样式 **/
const addStyle = (instance: SpinInstance) => {
  const maskStyle: CSSProperties = {
    position: 'fixed',
    zIndex: 2000,
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    backgroundColor: 'rgba(0, 0, 0, 0.7)',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  };
  for (const [key, value] of Object.entries(maskStyle)) {
    // instance.$el.style的属性类型: https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
    instance.$el.style[(key as unknown as number)] = value;
  }
}

export default {
  service,
};

新建 Spin/types.ts 文件:

javascript 复制代码
import type { SpinProps } from 'ant-design-vue';
import { createSpinComponent } from './index';

/** @name 通过交叉类型(&)扩展已有的类型声明 **/
export type SpinConfig = SpinProps & {
  target?: HTMLElement | string;
};

/** @name 获取createSpinComponent函数的返回值类型 **/
export type SpinInstance = ReturnType<typeof createSpinComponent>;

具体使用:

javascript 复制代码
import Spin from '@/components/Spin';

const spinIntance = Spin.service();

spinIntance.close();

结尾

它的使用方式基本上和 element-plus 的服务方式调用一样,代码过程也差不多,其实小编就是照着它来写的,依葫芦画瓢。😉

而且,原本 ant-design-vue 支持的配置,一样也都支持,也一样都有快捷提示。

并且,小编还在其中扩展了一个 target 属性,它也是从 element-plus 那边参考过来的,具体用法如下说明:

有了这个扩展,你可以顺着这个属性继续定义其他一些自定义配置。

那么,以上便是全部源码了,源码中也都写上了详细的注释,应该不是很难啦,快去试试吧。😉


至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。

相关推荐
Jiaberrr6 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ2 小时前
html+css+js实现step进度条效果
javascript·css·html
程序员大金3 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
john_hjy3 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd3 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
It'sMyGo3 小时前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式
xgq4 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试