Vue 自定义指令详解
Vue 自定义指令详解
- 一、核心概念
- 1.内置指令和自定义指令
- 2. 作用和原理
- 二、注册方式
- 1. 全局注册 (`app.directive`)
- 2. 局部注册 (`directives` 选项)
- 三、指令钩子函数 (生命周期)
- 1.触发时机
- 2.常用钩子说明
- 四、钩子函数参数
- 五、功能强大的示例
- 1. 带参数和修饰符的焦点指令
- 2. 图片懒加载指令
- 3. 权限控制指令 (简化版)
- 六、函数简写
- 七、对象字面量
- 八、使用建议
- 九、总结
Vue 的自定义指令允许开发者注册可复用的指令,直接作用于 DOM 元素。它们是操作底层 DOM 的理想选择,适用于需要直接与元素交互的场景,如聚焦输入框、添加事件监听器、实现懒加载等。
一、核心概念
1.内置指令和自定义指令
- 内置指令:
v-model
,v-show
,v-if
,v-for
等。 - 自定义指令: 开发者根据需求创建的以
v-
开头的指令,用于封装特定的 DOM 操作逻辑。
2. 作用和原理
- 作用:封装DOM操作/事件逻辑,复用交互行为(如聚焦、拖拽),避免组件内直接操作DOM。
- 原理:通过指令名(如v-focus)绑定到元素,响应生命周期钩子或值变化。
二、注册方式
1. 全局注册 (app.directive
)
在应用实例上注册,所有组件均可使用。
import { createApp } from 'vue'const app = createApp({})// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {// 指令的钩子函数mounted(el) {el.focus() // 聚焦元素}
})
2. 局部注册 (directives
选项)
在组件内部注册,仅当前组件可用。
<template><input v-focus />
</template><script>
export default {directives: {focus: {mounted(el) {el.focus()}}}
}
</script>
三、指令钩子函数 (生命周期)
1.触发时机
自定义指令提供了多个钩子函数,在不同阶段执行:
钩子 | 触发时机 | 参数 |
---|---|---|
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 统一为上述名称。
2.常用钩子说明
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
中可用)。
五、功能强大的示例
1. 带参数和修饰符的焦点指令
app.directive('focus', {mounted(el, binding) {const { value, arg, modifiers } = bindingif (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 后聚焦- 聚焦后自动全选文本内容
-->
2. 图片懒加载指令
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" />
3. 权限控制指令 (简化版)
// 假设有一个全局的权限检查函数
function hasPermission(permission) {// 返回用户是否有该权限return userPermissions.includes(permission)
}app.directive('permission', {mounted(el, binding) {const requiredPerm = binding.valueif (!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
钩子中务必清理所有副作用(事件监听器、定时器、观察器等)。 - 避免复杂逻辑: 指令应专注于 DOM 操作。复杂的业务逻辑应放在组件或 Composition API 中。
- 类型安全 (TypeScript): 可以通过模块扩展为自定义指令提供类型提示。
// augment.d.ts
import { Directive } from 'vue'declare module 'vue' {export interface Directives {focus: Directivelazy: Directive}
}
九、总结
Vue 自定义指令是操作原生 DOM 的强大工具。
- 何时使用: 当需要直接、低级别的 DOM 操作,且该操作具有通用性时。
- 核心: 掌握钩子函数和参数 (
el
,binding
)。 - 关键: 在
unmounted
钩子中进行资源清理,防止内存泄漏。 - 趋势: 在 Vue 3 Composition API 和 Teleport 等新特性下,部分传统指令场景(如模态框)有了更现代的解决方案,但指令在聚焦、懒加载、权限等场景依然不可替代。