Vue 自定义指令(Custom Directives)是一种扩展 Vue 功能的方式,允许你直接操作 DOM 元素,用于处理一些常见的 DOM 操作逻辑(如聚焦、权限控制、动画等)。
自定义指令的基本结构
自定义指令可以注册为全局指令或局部指令,包含一系列钩子函数(类似组件生命周期)。
1. 全局注册
javascript
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
2. 局部注册
javascript
export default {
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
}
使用方式
vue
<template>
<input v-focus />
</template>
指令的钩子函数
指令有 5 个钩子函数(Vue 2):
钩子函数 | 说明 |
---|---|
bind |
只调用一次,指令第一次绑定到元素时调用 |
inserted |
被绑定元素插入父节点时调用 |
update |
所在组件的 VNode 更新时调用(可能发生在子 VNode 更新前) |
componentUpdated |
所在组件的 VNode 及其子 VNode 全部更新后调用 |
unbind |
只调用一次,指令与元素解绑时调用 |
钩子函数参数:
el
:指令所绑定的元素,可以直接操作 DOMbinding
:一个对象,包含指令相关信息(如name
、value
、expression
等)vnode
:Vue 编译生成的虚拟节点oldVnode
:上一个虚拟节点(仅在update
和componentUpdated
钩子中可用)
实用示例
1. 权限控制指令(v-permission)
javascript
// 全局注册 v-permission
Vue.directive('permission', {
inserted(el, binding) {
// 获取用户权限列表(实际项目中可能从 Vuex 或接口获取)
const userPermissions = ['edit', 'delete']
// 获取指令绑定的值(如 v-permission="['delete']")
const requiredPermissions = binding.value
// 检查是否有权限,无权限则移除元素
const hasPermission = requiredPermissions.some(perm =>
userPermissions.includes(perm)
)
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
})
vue
<!-- 只有拥有 delete 权限的用户才会看到该按钮 -->
<button v-permission="['delete']">删除</button>
2. 防抖指令(v-debounce)
javascript
Vue.directive('debounce', {
bind(el, binding) {
const { value, arg = 500 } = binding // arg 为防抖时间,默认 500ms
let timer = null
el.addEventListener('click', () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
value() // 执行绑定的方法
}, arg)
})
}
})
vue
<!-- 点击后 1000ms 才执行 handleClick 方法 -->
<button v-debounce:[1000]="handleClick">提交</button>
3. Vue 3 中的自定义指令
Vue 3 中指令钩子函数有所调整,更贴近组合式 API:
javascript
// Vue 3 全局注册
import { createApp } from 'vue'
const app = createApp(App)
app.directive('focus', {
// 在元素挂载后执行(替代 Vue 2 的 inserted)
mounted(el) {
el.focus()
}
})
Vue 3 指令钩子:created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeUnmount
、unmounted
动态指令参数
指令可以接收动态参数,使指令更灵活:
vue
<!-- 动态设置背景色 -->
<div v-bg:[color]="'red'"></div>
<script>
Vue.directive('bg', {
bind(el, binding) {
// binding.arg 为动态参数(color 的值)
// binding.value 为指令值('red')
el.style[binding.arg] = binding.value
}
})
export default {
data() {
return { color: 'backgroundColor' }
}
}
</script>
注意事项
- 自定义指令主要用于底层 DOM 操作,若能通过组件或 props 实现的功能,优先使用组件
- 避免在指令中过度处理复杂逻辑,保持指令单一职责
- Vue 3 中指令的钩子函数与组件生命周期更一致,使用时注意区分
通过自定义指令,可以封装通用的 DOM 操作逻辑,提高代码复用性。
vue3的自定义指令钩子函数
在 Vue 3 中,自定义指令的钩子函数进行了调整,命名更贴近组件的生命周期,使其更易于理解和使用。以下是 Vue 3 中自定义指令的钩子函数及详细说明:
Vue 3 自定义指令的钩子函数
Vue 3 提供了 7 个指令钩子函数,按执行顺序排列如下:
钩子函数 | 触发时机 |
---|---|
created |
指令绑定到元素上且组件的属性或事件监听器被应用之前调用(很少使用) |
beforeMount |
指令绑定的元素被挂载到 DOM 之前调用 |
mounted |
指令绑定的元素被挂载到 DOM 后调用(最常用,替代 Vue 2 的 inserted ) |
beforeUpdate |
指令所在组件的 VNode 更新前调用(可能发生在子 VNode 更新前) |
updated |
指令所在组件的 VNode 及其子 VNode 全部更新后调用 |
beforeUnmount |
指令绑定的元素从 DOM 卸载前调用 |
unmounted |
指令绑定的元素从 DOM 卸载后调用(替代 Vue 2 的 unbind ) |
钩子函数的参数
每个钩子函数都接收以下 4 个参数(与 Vue 2 类似,但部分属性有调整):
-
el
:指令所绑定的 DOM 元素,可直接操作 DOM
binding
:包含指令信息的对象,结构如下:
-
value
:指令的绑定值(如v-directive="123"
中的123
) -
oldValue
:指令绑定的前一个值(仅在beforeUpdate
和updated
中可用) -
arg
:指令的参数(如v-directive:foo
中的foo
) -
modifiers
:指令的修饰符对象(如v-directive.foo.bar
中{ foo: true, bar: true }
) -
instance
:使用该指令的组件实例 -
dir
:指令的定义对象(包含钩子函数等) -
vnode
:Vue 编译生成的虚拟节点 -
prevVnode
:上一个虚拟节点(仅在beforeUpdate
和updated
中可用)
示例:实现一个 Vue 3 自定义指令
以下是一个完整示例
vue
<template>
<!-- 使用自定义指令 -->
<button v-copy="copyText" v-copy:success="onCopySuccess">
点击复制
</button>
</template>
<script setup>
import { ref } from 'vue'
import { copyDirective } from './directives'
// 注册局部指令
const directives = {
copy: copyDirective
}
// 要复制的文本
const copyText = ref('这是要复制的内容')
// 复制成功的回调
const onCopySuccess = (text) => {
alert(`复制成功:${text}`)
}
</script>
<!-- directives.js -->
<script>
export const copyDirective = {
// 元素挂载后
mounted(el, binding) {
// 存储要复制的文本和回调函数
el.copyText = binding.value
el.successCallback = binding.arg === 'success' ? binding.value : null
// 点击事件处理函数
el.handleClick = () => {
// 复制文本到剪贴板
navigator.clipboard.writeText(el.copyText)
.then(() => {
// 触发成功回调
if (el.successCallback && typeof el.successCallback === 'function') {
el.successCallback(el.copyText)
}
})
.catch(err => {
console.error('复制失败:', err)
})
}
// 绑定点击事件
el.addEventListener('click', el.handleClick)
},
// 组件更新时(值可能变化)
updated(el, binding) {
// 更新存储的文本和回调
el.copyText = binding.value
if (binding.arg === 'success') {
el.successCallback = binding.value
}
},
// 元素卸载前
beforeUnmount(el) {
// 移除事件监听,避免内存泄漏
el.removeEventListener('click', el.handleClick)
}
}
</script>
关键变化与注意事项
-
钩子命名更直观:
用
mounted
替代 Vue 2 的inserted
(元素挂载到 DOM 后触发),用unmounted
替代unbind
(元素卸载后触发),更符合组件生命周期的命名习惯。 -
支持组合式 API:
在
<script setup>
中,局部指令通过directives
对象注册,全局指令通过app.directive()
注册(如app.directive('copy', copyDirective)
)。 -
参数增强:
binding.instance
可以直接访问使用指令的组件实例,方便获取组件内的数据或方法。 -
清理逻辑:
需在
beforeUnmount
或unmounted
中移除事件监听、定时器等,避免内存泄漏(如示例中移除点击事件)。
常见使用场景
- 操作 DOM(如自动聚焦
v-focus
、滚动加载v-infinite-scroll
) - 权限控制(如
v-permission
决定元素是否显示) - 交互增强(如
v-longpress
长按指令、v-debounce
防抖指令)
Vue 3 的自定义指令钩子设计更符合直觉,同时保留了灵活操作 DOM 的能力,是扩展 Vue 功能的重要方式。