前言
什么是指令?
类似于v-model
或 v-show
,这些是 Vue 内置的一系列指令,那除了内置指令之外,Vue 还允许我们自己注册自己所需要的指令。
今天就借助 v-loading 的效果来实现一个我们自己的 v-myLoading 吧!
效果

原理
自定义指令的相关知识大家可以去 Vue3 官方文档看一下:
首先我们知道我们需要实现的是 v-loading,那一个 loading 效果是什么样的呢?

可以清楚的看到,需要在目标区域垂直水平居中
,并且使背景色
为不完全透明的白色。
背景色好搞定,那如何使 loading 永远居中在目标容器内呢?
我们知道,如果子元素使用绝对定位,并且父元素为非 static
定位的话,便可充当参照物,就可以通过定位将子元居中在目标区域内。
那接下来我们需要做的就是:
- 创建 loading 组件
- 找到目标元素,把 loading 组件添加进去。
- 为目标元素设置为参照物属性,使其垂直居中显示。
- 对 loading 状态进行开关控制。
实现
首先创建一个用于页面显示的 loading 组件,下面核心代码,在页面中增加一个 loading 图标,并且设置无限旋转
的动画即可。
js
.loading-icon {
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

下面是我们的文件夹结构

接下来分析一下 src\plugin\directive\loading\index.js 也就是自定义指令 v-myLoading 文件里的代码
js
export default {
name: 'myLoading',
mounted(el, binding) {
const app = createApp(Loading)
const instance = app.mount(document.createElement('div'))
console.log('instance: ', instance.$el)
const name = Loading.name
if (!el[name]) {
el[name] = {}
}
el[name].instance = instance
if (binding.value) {
append(el)
}
},
updated(el, binding) {
const name = Loading.name
if (!el[name]) {
el[name] = {}
}
if (binding.value !== binding.oldValue) {
binding.value ? append(el) : remove(el)
}
},
}
function append(el) {
const style = getComputedStyle(el)
const name = Loading.name
if (['absolute', 'fixed', 'relative'].indexOf(style.position) === -1) {
if (!el.classList.contains(relativeClsss)) {
el.classList.add(relativeClsss)
}
}
el.appendChild(el[name].instance.$el)
}
function remove(el) {
const name = Loading.name
el.classList.remove(relativeClsss)
el.removeChild(el[name].instance.$el)
}
先分析一下 mounted 里面的代码
el: 目标元素
binding: 使用指令时所传的值 v-loading="true",此时 binding.value 为 true
instance: loading 组件实例
js
mounted(el, binding) {
const app = createApp(Loading)
const instance = app.mount(document.createElement('div'))
console.log('instance: ', instance.$el)
const name = Loading.name
if (!el[name]) {
el[name] = {}
}
el[name].instance = instance
if (binding.value) {
append(el)
}
},
首先创建 loading 组件的 Vue 实例 app,并挂载到临时 div 元素上,mount 方法会返回相应的组件实例赋值给 instance 变量,通过 instance.$el 拿到我们的 loading 组件元素。
js
const app = createApp(Loading)
const instance = app.mount(document.createElement('div'))
console.log('instance: ', instance.$el)
下面是打印的 instance 变量 ,可以看到已经拿到 loading 元素了;
检查目标元素上是否已经绑定了该组件实例,如果没有则初始化一个对象。
在这个对象上挂载当前的组件实例 instance
,这一步是为了后面把 loading 组件添加到目标元素内
。
js
const name = Loading.name
if (!el[name]) {
el[name] = {}
}
el[name].instance = instance
判断loading初始状态,如果为true,就开启loading,把 loading 组件添加到目标元素内部
js
if (binding.value) {
append(el)
}
一般我们的 loading 状态初始值都为 false,在我们调用接口的时候赋值为 true,接口调用结束赋值为 false,那我们就来看一下怎么处理 loading 状态的变化,这个时候需要用到 updated
函数,他会在 loadnig 状态变化时执行
。
当 loading 状态变化时,如果为 true,就把 loading 添加进去,如果为 false 就移除出去
js
updated(el, binding) {
const name = Loading.name
if (!el[name]) {
el[name] = {}
}
if (binding.value !== binding.oldValue) {
binding.value ? append(el) : remove(el)
}
},
}
添加元素函数:
判断目标元素是否含有非 static 定位,如果都没有就增加一个相对定位,g-relative
为我们事先写好的全局相对定位 class。最后把 loading 组件添加到目标元素里面。
css
.g-relative {
position: relative;
}
js
const relativeClsss = 'g-relative'//为相对定位
function append(el) {
const style = getComputedStyle(el)
const name = Loading.name
if (['absolute', 'fixed', 'relative'].indexOf(style.position) === -1) {
if (!el.classList.contains(relativeCls)) {
el.classList.add(relativeCls)
}
}
el.appendChild(el[name].instance.$el)
}
移除元素函数:
这个就直接移除相对定位 class,移除loading组件即可
js
function remove(el) {
const name = Loading.name
el.classList.remove(relativeClsss)
el.removeChild(el[name].instance.$el)
}
怎么使用?
在 plugin/directive/index.js
,引入我们写好的自定义文件,使用循环
的方式,这样方便我们其他的指令可以一起全局注册使用。
js
import loadingDirective from '@/plugin/directive/loading/index.js'
import testDirective from '@/plugin/directive/test/index.js'
const directiveList = [loadingDirective, testDirective]
export default {
install: (app) => {
directiveList.forEach((component) => {
app.directive(component.name, component)
})
},
}
最后在 main.js
中使用即可
js
import directive from './plugin/directive/index.js'
app.use(directive).mount('#app')
总结
在实现一个自定义指令的时候,我们首先要明白在做的东西是什么,如果使用 js 的话应该怎么实现?其内部原理还是 js,就好比一个简单的 input 自动聚焦指令,mounted: (el) => el.focus(),只需要一行代码就行了。其次就是对于自定义指令的内部一些参数、方法知道如何使用,这个需要我们做过一次,就明白了。
源码:
GitHub地址:chenyajun-create/v-myLoading (github.com)
所有源码已经同步提交 GitHub,如果觉得对你有帮助或者启发的话,可以给作者点下 star⭐