写在开头
最近接手一个 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
那边参考过来的,具体用法如下说明:
有了这个扩展,你可以顺着这个属性继续定义其他一些自定义配置。
那么,以上便是全部源码了,源码中也都写上了详细的注释,应该不是很难啦,快去试试吧。😉
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。