以下是分别针对 Vue2 和 Vue3 的 v-loading 全局自定义指令实现:
Vue2 实现
// src/directives/loading.js
export default {
// 指令绑定到元素时触发
bind(el, binding) {
this.init(el, binding)
},
// 元素插入 DOM 时触发
inserted(el, binding) {
this.update(el, binding)
},
// 指令值更新时触发
update(el, binding) {
if (binding.value) {
this.showLoading(el, binding)
} else {
this.hideLoading(el)
}
},
// 指令卸载时触发
unbind(el) {
this.destroy(el)
},
// 初始化加载层
init(el, binding) {
// 创建加载层容器
this.loadingDiv = document.createElement('div')
this.loadingDiv.className = 'v-loading-container'
this.loadingDiv.style.position = 'absolute'
this.loadingDiv.style.inset = '0'
this.loadingDiv.style.backgroundColor = 'rgba(255,255,255,0.8)'
this.loadingDiv.style.display = 'none'
this.loadingDiv.style.justifyContent = 'center'
this.loadingDiv.style.alignItems = 'center'
this.loadingDiv.style.pointerEvents = 'none'
// 创建加载动画元素
const spinner = document.createElement('div')
spinner.className = 'v-loading-spinner'
spinner.style.border = '4px solid #2c3e50'
spinner.style.borderTopColor = 'transparent'
spinner.style.borderRadius = '50%'
spinner.style.width = '24px'
spinner.style.height = '24px'
spinner.style.animation = 'rotate 1s linear infinite'
// 创建文本元素
const textDiv = document.createElement('div')
textDiv.textContent = '加载中...'
textDiv.style.marginLeft = '16px'
textDiv.style.color = '#2c3e50'
// 组合元素
this.loadingDiv.appendChild(spinner)
this.loadingDiv.appendChild(textDiv)
// 将加载层插入到目标元素中
el.appendChild(this.loadingDiv)
},
// 显示加载层
showLoading(el, binding) {
if (this.loadingDiv) {
// 动态更新配置
const { color, text, size } = binding.modifiers
if (color) this.loadingDiv.querySelector('.v-loading-spinner').style.borderColor = ${color} transparent
if (text) this.loadingDiv.querySelector('.v-loading-text').textContent = text
if (size) {
this.loadingDiv.querySelector('.v-loading-spinner').style.width = ${size}px
this.loadingDiv.querySelector('.v-loading-spinner').style.height = ${size}px
}
this.loadingDiv.style.display = 'flex'
}
},
// 隐藏加载层
hideLoading(el) {
if (this.loadingDiv) {
this.loadingDiv.style.display = 'none'
}
},
// 销毁加载层
destroy(el) {
if (this.loadingDiv) {
el.removeChild(this.loadingDiv)
this.loadingDiv = null
}
}
}
全局注册(main.js):
import Vue from 'vue'
import LoadingDirective from './directives/loading'
Vue.directive('loading', LoadingDirective)
Vue3 实现
// src/directives/loading.js
import { createVNode, render } from 'vue'
export default {
// 指令绑定到元素时触发
mounted(el, binding) {
this.init(el, binding)
},
// 指令值更新时触发
updated(el, binding) {
this.update(el, binding)
},
// 指令卸载时触发
unmounted(el) {
this.destroy(el)
},
// 初始化加载层
init(el, binding) {
// 创建加载层虚拟节点
this.loadingVNode = createVNode('div', {
class: 'v-loading-container',
style: {
position: 'absolute',
inset: '0',
backgroundColor: 'rgba(255,255,255,0.8)',
display: 'none',
justifyContent: 'center',
alignItems: 'center',
pointerEvents: 'none'
}
}, [
createVNode('div', {
class: 'v-loading-spinner',
style: {
border: '4px solid #2c3e50',
borderTopColor: 'transparent',
borderRadius: '50%',
width: '24px',
height: '24px',
animation: 'rotate 1s linear infinite'
}
}),
createVNode('div', {
class: 'v-loading-text',
style: { marginLeft: '16px', color: '#2c3e50' }
}, '加载中...')
])
// 将加载层插入到目标元素中
render(this.loadingVNode, el)
},
// 更新加载状态
update(el, binding) {
if (binding.value) {
this.showLoading(el, binding)
} else {
this.hideLoading(el)
}
},
// 显示加载层
showLoading(el, binding) {
if (this.loadingVNode) {
// 动态更新配置
const { color, text, size } = binding.modifiers
if (color) {
this.loadingVNode.children[0].props.style.borderColor = ${color} transparent
}
if (text) {
this.loadingVNode.children[1].children = text
}
if (size) {
this.loadingVNode.children[0].props.style.width = ${size}px
this.loadingVNode.children[0].props.style.height = ${size}px
}
this.loadingVNode.props.style.display = 'flex'
render(this.loadingVNode, el)
}
},
// 隐藏加载层
hideLoading(el) {
if (this.loadingVNode) {
this.loadingVNode.props.style.display = 'none'
render(this.loadingVNode, el)
}
},
// 销毁加载层
destroy(el) {
if (this.loadingVNode) {
render(null, el)
this.loadingVNode = null
}
}
}
全局注册(main.js):
import { createApp } from 'vue'
import LoadingDirective from './directives/loading'
createApp(App).directive('loading', LoadingDirective).mount('#app')
关键差异说明
特性 Vue2 实现 Vue3 实现
生命周期钩子 bind/inserted/update/unbind mounted/updated/unmounted
DOM 操作 直接创建原生 DOM 元素 使用 createVNode 和 render 函数操作虚拟 DOM
响应式更新 通过 update 钩子检测值变化 通过 updated 钩子自动响应 binding.value 变化
配置传递 使用 binding.modifiers 传递参数(如 v-loading.color) 支持 binding.arg 和 binding.modifiers 混合使用
内存管理 手动移除 DOM 元素 通过 render(null, el) 自动清理
动画实现 直接操作 style.animation 推荐使用 CSS 类名或 @keyframes 定义动画
使用示例
<button
v-loading="isLoading"
v-loading.color="#42b983"
v-loading.text="提交中..."
v-loading.size="32"
@click="handleSubmit"
提交
根据项目使用的 Vue 版本选择对应的实现方式即可。Vue3 版本更推荐使用虚拟 DOM 操作,而 Vue2 版本则直接操作原生 DOM。