v-loading 指令通过 useLoading 组合函数封装加载状态逻辑,支持响应式绑定、延迟显示、防抖隐藏及 DOM 生命周期管理,无需插件即可在 Vue 3 中开箱即用。

用组合式 API 封装一个 v-loading 指令,核心是把加载状态的控制逻辑从组件中抽离,通过指令自动绑定、响应式更新和 DOM 生命周期管理来实现复用。不需要额外安装插件,Vue 3 原生支持自定义指令 + useLoading 组合函数即可开箱即用。
封装可复用的 useLoading 组合函数
先写一个逻辑组合函数,负责管理 loading 状态、延迟显示、防抖隐藏等基础能力:
- 接收初始状态(
boolean或ref)、延迟毫秒(避免闪烁)、是否遮罩等配置 - 返回
loading响应式引用、start/stop方法,以及一个用于指令内部调用的bind函数 - 使用
onBeforeUnmount清理定时器,避免内存泄漏
示例代码:
import { ref, onBeforeUnmount } from 'vue'
export function useLoading(initial = false, delay = 200) {
const loading = ref(initial)
let timer = null
const start = () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => (loading.value = true), delay)
}
const stop = () => {
if (timer) clearTimeout(timer)
loading.value = false
}
onBeforeUnmount(() => {
if (timer) clearTimeout(timer)
})
return {
loading,
start,
stop,
// 指令内部需要的绑定方法(稍后用到)
bind: (el, binding) => {
const value = binding.value
if (value === true || value === false) {
loading.value = value
} else if (typeof value === 'function') {
// 支持传入计算函数:v-loading="() => userList.length === 0"
loading.value = value()
}
}
}
}
编写 v-loading 自定义指令
利用 Vue 3 的指令生命周期钩子,在 mounted 中初始化 loading 状态,在 updated 中响应绑定值变化,在 unmounted 中清理 DOM 节点:
- 指令内部创建一个独立的
useLoading实例,避免跨组件污染 - 手动插入 loading 遮罩层(含 spinner),并设置样式为绝对定位、覆盖内容
- 监听
binding.value变化,调用bind方法更新状态 - 支持对象语法:
v-loading="{ active: isLoading, text: '加载中...' }"
完整指令定义(可直接复制进 directives/loading.js):
import { createApp, onMounted, onUpdated, onUnmounted } from 'vue'
import { useLoading } from '@/composables/useLoading'
const loadingDirective = {
mounted(el, binding) {
const { loading, bind, start, stop } = useLoading(false)
// 创建遮罩层
const mask = document.createElement('div')
mask.className = 'v-loading-mask'
mask.innerHTML = `
<div class="v-loading-spinner"></div>
<div class="v-loading-text">${binding.value?.text || '加载中...'}</div>
`
// 样式注入(仅首次)
if (!document.getElementById('v-loading-style')) {
const style = document.createElement('style')
style.id = 'v-loading-style'
style.textContent = `
.v-loading-mask {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(255, 255, 255, 0.8); display: flex;
flex-direction: column; justify-content: center; align-items: center;
z-index: 1000; transition: opacity 0.2s;
}
.v-loading-spinner {
width: 24px; height: 24px; border: 2px solid #ccc;
border-top-color: #409eff; border-radius: 50%;
animation: v-loading-rotate 1s linear infinite;
}
.v-loading-text { margin-top: 8px; font-size: 14px; color: #666; }
@keyframes v-loading-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
`
document.head.appendChild(style)
}
// 绑定事件 & 插入遮罩
const updateMask = () => {
if (loading.value) {
el.style.position = el.style.position === 'static' ? 'relative' : el.style.position
el.appendChild(mask)
} else {
mask.remove()
}
}
// 初始绑定
bind(el, binding)
updateMask()
// 暴露给指令实例
el._loading = {
loading,
updateMask,
start,
stop,
mask
}
// 监听响应式变化(需配合 watch)
},
updated(el, binding) {
const { loading, bind, updateMask } = el._loading
bind(el, binding)
updateMask()
},
unmounted(el) {
el._loading?.mask?.remove()
}
}
export default loadingDirective
全局注册并使用
在 main.js 中注册指令:
import loadingDirective from './directives/loading'
const app = createApp(App)
app.directive('loading', loadingDirective)
然后在任意模板中使用:
- 基础用法:
<div v-loading="isLoading"></div> - 函数用法:
<div v-loading="() => !userInfo.id"></div> - 对象配置:
<div v-loading="{ active: isPending, text: '提交中...' }"></div> - 搭配业务逻辑:
<button v-loading="loadingState">加载</button>,其中loadingState是一个 ref
进阶优化建议
让这个指令更健壮、易维护:
- 支持局部注册:在单个组件内
directives: { loading: loadingDirective } - 增加节流控制:对高频触发的 loading(如搜索输入)加防抖开关
- 透传 class / style:允许通过
v-loading:class动态设置遮罩样式 - 与 axios 请求拦截联动:在
request拦截器中调用start(),response中调用stop(),但注意需指定作用域元素
不复杂但容易忽略。关键在于把状态管理交给组合式函数,DOM 操作交给指令,职责清晰,改一处、全局生效。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










