自定义指令
本教程共 40 篇 · 第 30 篇 · 更新于 2026-07-29 · 约 6 分钟阅读
30. 自定义指令
本节目标:搞清楚什么时候该用自定义指令,怎么用,以及它的生命周期钩子怎么运作。
自定义指令是什么
Vue 内置了 v-model、v-show、v-if 等指令。当你需要直接操作 DOM 元素来完成某个功能时,自定义指令就派上用场了。
它跟组件、组合式函数并列为 Vue 的三种代码复用方式:
| 方式 | 用途 |
|---|---|
| 组件 | 复用 UI + 逻辑 |
| 组合式函数 | 复用有状态逻辑 |
| 自定义指令 | 复用 DOM 底层操作 |
一个例子:自动聚焦
假设你希望页面加载后 input 自动聚焦。HTML5 有 autofocus 属性,但它只在初次加载时生效。如果元素是 Vue 动态插入的,autofocus 就不管用了。
自定义指令来解决:
<script setup>
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
Tip在
<script setup>里,以v开头的驼峰变量会自动识别为自定义指令。vFocus在模板里写成v-focus。
生命周期钩子
自定义指令有一组跟组件类似的钩子,在元素的不同阶段触发:
const myDirective = {
// 元素绑定到组件、但 DOM 还没插入时
created(el, binding, vnode) {},
// 插入 DOM 之前
beforeMount(el, binding, vnode) {},
// 父组件及子元素都插入到 DOM 后
mounted(el, binding, vnode) {},
// 父组件更新之前
beforeUpdate(el, binding, vnode, prevVnode) {},
// 父组件及子元素都更新后
updated(el, binding, vnode, prevVnode) {},
// 父组件卸载之前
beforeUnmount(el, binding, vnode) {},
// 父组件卸载时
unmounted(el, binding, vnode) {}
}
Note这些钩子都是可选的。实际开发中大部分场景只用到
mounted和updated。
钩子参数
每个钩子都能拿到这些参数:
| 参数 | 说明 |
|---|---|
el | 指令绑定的 DOM 元素,直接操作 |
binding | 包含指令相关信息的对象 |
vnode | 元素的虚拟 DOM 节点 |
prevVnode | 上一次渲染的虚拟节点(仅 beforeUpdate 和 updated) |
binding 对象里有这些属性:
| 属性 | 说明 | 例子 |
|---|---|---|
value | 指令接收的值 | v-demo="1+1" → 2 |
oldValue | 上一次的值 | 仅 beforeUpdate / updated |
arg | 指令参数 | v-demo:foo → "foo" |
modifiers | 修饰符对象 | v-demo.foo.bar → { foo: true, bar: true } |
instance | 使用该指令的组件实例 | |
dir | 指令的定义对象本身 |
举例说明,如果你这样写模板:
<div v-example:foo.bar="baz"></div>
那么 binding 对象就是:
{
arg: 'foo',
modifiers: { bar: true },
value: /* baz 的值 */
}
Note除了
el,其他参数都应该是只读的。别去改它们,要用el.dataset来跨钩子共享信息。
动态参数
指令参数也可以是响应式的:
<div v-example:[arg]="value"></div>
当你改变 arg 的值时,Vue 会重新执行指令的更新逻辑。
函数简写
很多指令在 mounted 和 updated 时做的事情完全一样,其他钩子用不到。这时候可以写成函数形式:
app.directive('color', (el, binding) => {
// mounted 和 updated 都会调用这段逻辑
el.style.color = binding.value
})
模板里使用:
<div v-color="color"></div>
对象字面量传值
如果想传多个值,可以直接传一个对象:
<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!'
})
局部注册 vs 全局注册
前面 <script setup> 里直接定义变量是局部注册——只能当前组件用。如果想全局生效,在 main.js 里注册:
import { createApp } from 'vue'
const app = createApp({})
app.directive('highlight', {
mounted(el) {
el.style.backgroundColor = 'yellow'
}
})
在组件上用自定义指令
自定义指令可以用于组件,但只会作用于组件的根元素。
Warning不推荐在组件上使用自定义指令。如果组件有多个根节点,指令会被忽略并抛出警告。
什么时候该用
自定义指令是”最后手段”。能用 v-bind 等内置指令解决就别自己造轮子。适合的场景包括:
- 自动聚焦输入框
- 元素滚动到可视区域
- 元素外部点击检测
- 权限控制(没有权限就隐藏)
这些场景的共同点是:必须直接操作 DOM,没有别的声明式替代方案。
本节回顾
- 自定义指令用于复用低级别 DOM 操作
- 有 7 个生命周期钩子,
mounted最常用 binding对象包含value、arg、modifiers等- 函数简写适合
mounted和updated逻辑一致的场景 - 全局用
app.directive(),局部在<script setup>里用v前缀变量 - 优先考虑内置指令,自定义指令是兜底方案