发布于 

编写vue3插件,实现一个全局满屏loading

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。

插件没有严格定义的使用范围,app.use(xxx)就是在使用插件了。

官方已经为我们总结出来了,插件发挥作用的常见场景主要包括以下几种:

  • 通过app.component()app.directive()注册一到多个全局组件或自定义指令。
  • 通过app.provide()使一个资源可被注入进整个应用
  • app.config.globalProperties中添加一些全局实例属性或方法

常见并且为我们熟悉插件的有:app.use(router)app.use(pinia)

像element-plus中的loading全屏加载和message消息提示,都可封装为一个插件来使用。下面我将使用插件的方式,封装一个类似于element-plus中的Loading全屏加载插件。这个插件可以在多处地方使用,像页面加载、数据加载、用户操作等待响应这三种场景,我们都可以使用这个loading插件来提高用户体验。

Loading插件代码实现

首先在src目录的components下的目录新建Loading.vue和Loaidng.js两个文件。

Loading.vue为一个组件,里面放置loading效果实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<template>
<!-- 遮罩效果 -->
<div class="cover" v-if="showCover">
<span class="loader-3">Load&nbsp;ng</span>
</div>
</template>

<script setup>
import { ref, reactive } from "vue";
//showCover为响应式,可以被组件watch监听
const showCover = ref(false);
//控制显示
const handlerShow = () => showCover.value = true;
//控制隐藏
const handlerHide = () => showCover.value = false;
;

//将变量或方法抛出,使外界可访问
defineExpose({
showCover,
handlerHide,
handlerShow,
});
</script>

<style scoped>
.cover {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 9;
display: flex;
align-items: center;
justify-content: center;
}
.loader-3 {
color: #42b983;
position: relative;
font-family: Arial, Helvetica, sans-serif;
font-size: 48px;
/* letter-spacing: 48px; */
letter-spacing: 4px;
top: -50px;
/* font-weight: bold; */
}
.loader-3::before {
content: "";
position: absolute;
right: 70px;
bottom: 10px;
height: 28px;
width: 5.15px;
background: currentColor;
animation: loaderL 1s linear infinite alternate;
}
.loader-3::after {
content: "";
width: 10px;
height: 10px;
position: absolute;
left: 125px;
top: 2px;
border-radius: 50%;
background: #42b983;
animation: animloader113 1s linear infinite alternate;
}

@keyframes loaderL {
0% {
box-shadow: 0 -6px, -122.9px -8px;
}
25%,
75% {
box-shadow: 0 0px, -122.9px -8px;
}
100% {
box-shadow: 0 0px, -122.9px -16px;
}
}
@keyframes animloader113 {
0% {
transform: translate(0px, 0px) scaleX(1);
}
14% {
transform: translate(-12px, -16px) scaleX(1.05);
}
28% {
transform: translate(-27px, -28px) scaleX(1.07);
}
42% {
transform: translate(-46px, -35px) scaleX(1.1);
}
57% {
transform: translate(-70px, -37px) scaleX(1.1);
}
71% {
transform: translate(-94px, -32px) scaleX(1.07);
}
85% {
transform: translate(-111px, -22px) scaleX(1.05);
}
100% {
transform: translate(-125px, -9px) scaleX(1);
}
}
</style>

然后在Loading.js中导入Loading组件,注册组件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import Loading from './Loading.vue'
import { createVNode, render } from 'vue'

//导出对象,对象里面必须要有install方法,才能使用app.use()
export default {
//插件访问
install(app) {
//创建虚拟dom
const Vnode = createVNode(Loading)
//将loading挂载在body下,不可将其挂载在vue组件内,因为loading注册时,vue组件还没挂载到dom上
render(Vnode, document.body)
//添加一个可以在任意组件中访问的全局property,$loading可以调用loading暴露出来的方法或变量
app.config.globalProperties.$loading = {
show: Vnode.component.exposed.handlerShow,
hide: Vnode.component.exposed.handlerHide,
showCover: Vnode.component.exposed.showCover
}
},
//函数访问
//如果是使用函数访问且需要新建Dom元素进行挂载,
//必须将loading设置为单例,否则每次触发都会新建Dom元素
//如果直接挂载到body上,即:render(vm, document.body);无需设置单例
singleton: null,
service({ isShow = false }) {
//如果该实例存在,就调用对应方法并返回,避免再创建新的div
if (this.singleton) return this.singleton.show() && this.singleton

//初始化
//创建一个div
let container = document.createElement('div')
const vm = createVNode(Loading)
//渲染组件到container上
render(vm, container);
//添加container到body
document.body.appendChild(container);

//让singleton访问组件方法或属性
this.singleton = {
show: vm.component.exposed.handlerShow,
hide: vm.component.exposed.handlerHide
}
if (isShow) this.singleton.show()

return this.singleton
}
//组件内访问(插件)
// ...$loading.show();
// ...$loading.hide()
//函数访问,可以在axios拦截器中使用
/*例:
import Loading from './components/Loading.js'
//触发loading加载
let instance = Loading.service({isShow:true})
//5秒后关闭loading
setTimeout(() => {
instance.hide()
}, 5000);
*/
}

要想使用这个插件,还需要到main.js中use一下

1
2
3
import Loading from './components/Loading.js'
...
app.use(Loading)

在App.vue中通过点击按钮触发loading效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div class="container">
<button @click="handlerShowLoading">触发loading</button>
</div>
</template>

<script setup>
import { ref, getCurrentInstance, watch } from "vue";

//getCurrentInstance为组件实例,全局属性都会被挂载到实例上,通过proxy可以访问到全局属性
const { proxy } = getCurrentInstance();

const handlerShowLoading = () => {
//触发loading加载
proxy.$loading.show();
//5秒后关闭loading
setTimeout(() => {
proxy.$loading.hide();
}, 5000);
};

//使用watch可以监听loading暴露的响应式变量
watch(
() => proxy.$loading.showCover.value,
(newVal, oldVal) => {
console.log(newVal);
}
);
</script>

最终效果图如下:

loading演示