
Vue 的自定义指令允许开发者注册可复用的指令,直接作用于 DOM 元素。它们是操作底层 DOM 的理想选择,适用于需要直接与元素交互的场景,如聚焦输入框、添加事件监听器、实现懒加载等。
v-model, v-show, v-if, v-for 等。v- 开头的指令,用于封装特定的 DOM 操作逻辑。app.directive)在应用实例上注册,所有组件均可使用。
import { createApp } from 'vue'
const app = createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
// 指令的钩子函数
mounted(el) {
el.focus() // 聚焦元素
}
})directives 选项)在组件内部注册,仅当前组件可用。
<template>
<input v-focus />
</template>
<script>
export default {
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
</script>自定义指令提供了多个钩子函数,在不同阶段执行:
钩子 | 触发时机 | 参数 |
|---|---|---|
created | 指令绑定到元素后立即调用(仅 Vue 3)。在 beforeMount 之前。 | el, binding, vnode, prevVnode |
beforeMount | 元素挂载前调用(Vue 2 中相当于 bind) | el, binding, vnode |
mounted | 元素挂载后调用(最常用) | el, binding, vnode |
beforeUpdate | 元素更新前调用(仅 Vue 3) | el, binding, vnode, prevVnode |
updated | 元素更新后调用(Vue 2 中相当于 componentUpdated) | el, binding, vnode, prevVnode |
beforeUnmount | 元素卸载前调用(仅 Vue 3,相当于 Vue 2 的 unbind) | el, binding, vnode |
unmounted | 元素卸载后调用(清理工作在此进行) | el, binding, vnode |
注意: Vue 2 使用
bind,inserted,update,componentUpdated,unbind。Vue 3 统一为上述名称。
mounted: 最常用的钩子。元素已插入父 DOM,可以安全地进行 DOM 操作(如聚焦、初始化第三方库)。unmounted: 必须在此处清理资源!如移除事件监听器、清除定时器、销毁第三方实例,防止内存泄漏。每个钩子函数接收以下参数:
el: 指令绑定的元素。可以直接操作 DOM。binding: 一个对象,包含以下属性: value: 传递给指令的值。v-my-directive="1 + 1",则 binding.value 为 2。oldValue: 更新前的值(仅 beforeUpdate 和 updated 钩子中可用)。arg: 指令的参数。v-my-directive:arg,则 binding.arg 为 "arg"。modifiers: 包含修饰符的对象。v-my-directive.mod1.mod2,则 binding.modifiers 为 { mod1: true, mod2: true }。instance: 使用该指令的组件实例。dir: 指令的定义对象。vnode: 绑定元素对应的虚拟节点。prevVnode: 上一个虚拟节点(仅 beforeUpdate 和 updated 中可用)。app.directive('focus', {
mounted(el, binding) {
const { value, arg, modifiers } = binding
if (value) { // 只有值为真时才聚焦
el.focus()
// 参数: 指定延迟时间
if (arg) {
const delay = parseInt(arg)
setTimeout(() => el.focus(), delay)
}
// 修饰符: select 表示聚焦后全选文本
if (modifiers.select) {
el.select()
}
}
},
unmounted(el) {
// 清理(如果有必要)
}
})<input v-focus:500.select="shouldFocus" />
<!--
shouldFocus 为 true 时:
- 延迟 500ms 后聚焦
- 聚焦后自动全选文本内容
-->app.directive('lazy', {
mounted(el, binding) {
// 创建 IntersectionObserver 实例
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素进入视口,加载真实图片
el.src = binding.value
// 加载完成后停止观察
observer.unobserve(el)
}
})
})
// 开始观察元素
observer.observe(el)
// 将 observer 存储在元素上,便于后续清理
el._lazyObserver = observer
},
unmounted(el) {
// 卸载时必须断开观察,防止内存泄漏!
if (el._lazyObserver) {
el._lazyObserver.disconnect()
delete el._lazyObserver
}
}
})<img v-lazy="imageSrc" alt="Lazy Image" />// 假设有一个全局的权限检查函数
function hasPermission(permission) {
// 返回用户是否有该权限
return userPermissions.includes(permission)
}
app.directive('permission', {
mounted(el, binding) {
const requiredPerm = binding.value
if (!hasPermission(requiredPerm)) {
// 移除没有权限的元素
el.parentNode && el.parentNode.removeChild(el)
// 或者隐藏: el.style.display = 'none'
}
}
})<button v-permission="'delete_user'">删除用户</button>
<!-- 如果用户无 'delete_user' 权限,按钮将被移除 -->如果 mounted 和 updated 钩子逻辑相同,可以简写为一个函数。
app.directive('color', (el, binding) => {
// 等同于在 mounted 和 updated 钩子中执行
el.style.color = binding.value
})<p v-color="'red'">这段文字是红色的</p>如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量。别忘了,指令也可以接收任何合法的 JavaScript 表达式。
template
<div v-demo="{ color: 'white', text: 'hello!' }"></div>app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})v-focus, v-lazy, v-tooltip。unmounted 钩子中务必清理所有副作用(事件监听器、定时器、观察器等)。// augment.d.ts
import { Directive } from 'vue'
declare module 'vue' {
export interface Directives {
focus: Directive
lazy: Directive
}
}Vue 自定义指令是操作原生 DOM 的强大工具。
el, binding)。unmounted 钩子中进行资源清理,防止内存泄漏。