自定义指令
约 648 字大约 2 分钟
布欧-Lewyon
2026-05-15
当需要对 DOM 进行底层操作时(如自动聚焦、点击外部、拖拽),自定义指令是最直接的方式。
注册指令
全局注册
// main.js
import { createApp } from 'vue'
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus()
}
})局部注册
<script setup>
// 以 vFocus 命名(自动映射为 v-focus)
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>指令生命周期
<script setup>
const vMyDirective = {
// 绑定元素的父组件挂载时
created(el, binding, vnode, prevVnode) {},
// 元素被插入 DOM 时
beforeMount(el, binding, vnode, prevVnode) {},
// 元素挂载完成
mounted(el, binding, vnode, prevVnode) {
// binding: { value, oldValue, arg, modifiers, instance, dir }
el.focus()
console.log(binding.value) // 指令的值
console.log(binding.arg) // 指令参数 v-focus:foo → 'foo'
console.log(binding.modifiers) // 修饰符 v-focus.prevent → { prevent: true }
},
// 元素更新前
beforeUpdate(el, binding, vnode, prevVnode) {},
// 元素更新后
updated(el, binding, vnode, prevVnode) {},
// 元素卸载前
beforeUnmount(el, binding, vnode, prevVnode) {},
// 元素卸载后
unmounted(el, binding, vnode, prevVnode) {}
}
</script>实战指令
v-focus(自动聚焦)
<script setup>
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>v-click-outside(点击外部)
<script setup>
const vClickOutside = {
mounted(el, binding) {
el.__clickOutside = (event) => {
if (!el.contains(event.target)) {
binding.value(event)
}
}
document.addEventListener('click', el.__clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el.__clickOutside)
}
}
</script>
<template>
<div v-click-outside="() => isOpen = false" class="dropdown">
<button @click="isOpen = !isOpen">下拉菜单</button>
<div v-if="isOpen" class="menu">菜单内容</div>
</div>
</template>v-permission(权限指令)
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const vPermission = {
mounted(el, binding) {
const required = binding.value
const hasPermission = userStore.hasPermission(required)
if (!hasPermission) {
el.parentNode?.removeChild(el)
}
}
}
</script>
<template>
<button v-permission="'user:delete'" @click="deleteUser">删除用户</button>
<!-- 无权限时:该按钮从 DOM 中被移除 -->
</template>v-debounce(防抖输入)
<script setup>
const vDebounce = {
mounted(el, binding) {
const delay = binding.arg || 300
let timer
el.addEventListener('input', (event) => {
clearTimeout(timer)
timer = setTimeout(() => {
binding.value(event.target.value)
}, delay)
})
}
}
</script>
<template>
<input v-debounce:500="search" placeholder="防抖搜索" />
</template>v-lazy(图片懒加载)
<script setup>
const vLazy = {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
el.src = binding.value // 进入视口 → 替换真实 URL
observer.disconnect()
}
})
observer.observe(el)
}
}
</script>
<template>
<img v-lazy="'https://xxx.com/image.jpg'" />
</template>指令参数与修饰符
<template>
<!-- 值 -->
<div v-my-dir="someValue" />
<!-- 参数 -->
<div v-my-dir:foo="value" />
<!-- 修饰符 -->
<div v-my-dir.prevent.stop="value" />
<!-- 动态参数 -->
<div v-my-dir:[arg]="value" />
</template>在指令中读取:
const vMyDir = {
mounted(el, binding) {
binding.value // someValue
binding.arg // 'foo'
binding.modifiers // { prevent: true, stop: true }
}
}小结
| 概念 | 要点 |
|---|---|
| 注册 | 全局 app.directive / 局部 const vName |
| 生命周期 | mounted / updated / unmounted 等 |
| binding | value / arg / modifiers / instance |
| 应用场景 | 自动聚焦、点击外部、权限控制、防抖、懒加载 |
