你写了个登录框,希望页面一打开输入框就自动获得焦点,翻遍文档发现 v-model 管的是数据、v-if 管的是显示,没有一个内置指令能「直接操作这个 DOM 元素」。直接给你答案:这种「复用一段操作 DOM 的逻辑」的需求,正是自定义指令的用武之地——你只要注册一个 v-focus,就能在任何元素上复用「挂载后聚焦」的行为。今天这篇文章,编程狮用三个最常见场景,把自定义指令从概念到落地一次讲清。

一、什么时候该用自定义指令
先说结论:能用组件和组合式函数解决的事,优先别写指令。自定义指令的本质,是把「针对普通 DOM 元素的底层操作」封装成可复用的 v-xxx。它的适用边界很清晰——当这段逻辑必须直接碰 DOM(聚焦、监听滚动、绑定第三方库、懒加载图片),又不想每次都写一遍时,指令最合适。
举个例子,你做一个后台系统,十几个页面都有「输入框进页面自动聚焦」「按钮连点防抖」「长列表图片懒加载」。这些动作都绕不开原生 DOM API,如果每个页面手写一遍,又臭又长。把它们抽成自定义指令,模板里写个 v-focus、v-debounce、v-lazy,一行就搞定。
再补一句边界:如果只是「根据状态显示或隐藏元素」「计算派生数据」,那是计算属性和侦听器的活,不该写成指令。指令唯一正当的理由是「我要直接改这个 DOM 节点本身」,比如聚焦、滚动监听、绑定第三方库。把握住这条线,你就不会为了用指令而硬上指令,代码职责也清晰。
想系统建立 Vue 基础,建议先过一遍 Vue3 教程,把响应式、生命周期这些前置概念理顺,再来看指令会更顺。
二、局部自定义指令与全局自定义指令怎么选
自定义指令有两种注册方式,选错了会让代码变得难维护。局部指令写在单个组件的 <script setup> 里,只有当前组件能用;全局指令通过 app.directive 注册,全项目任意地方都能用。
经验法则很简单:只在一个地方用的指令,就地写成局部;超过两个页面都要用的,再提升为全局指令。比如 v-focus 如果全站表单都在用,就全局注册一次;某个报表页特有的「点击复制」逻辑,留在组件内即可。切忌一上来把所有指令都全局注册,时间久了没人记得谁还在用,重构时不敢删。
// 全局注册:一次定义,全项目可用(编程狮 www.w3cschool.cn 示例)
import { createApp } from 'vue'
const app = createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
},
})
这段把 v-focus 挂到应用实例上,之后任何组件写 v-focus 都生效。如果只在一个组件用,直接声明 const vFocus = { mounted(el){ el.focus() } } 即可,无需全局注册。更完整的语言基础可以看 Vue.js 教程,里面把实例与组件的关系讲得更透。
三、生命周期钩子:mounted、updated 与 unmounted
Vue3 的指令钩子和组件生命周期是一一对应的,这是新手第二个容易懵的点。Vue2 里叫 bind、inserted、update,到了 Vue3 改名成 mounted、updated、unmounted,语义更贴近组件,别照着老教程写错名字。
最常用的是这三个:mounted 在指令绑定的元素被插入父节点后触发,适合做初始化(比如聚焦、绑定监听);updated 在包含组件更新后触发,适合同步状态;unmounted 在元素被卸载前触发,必须在这里移除事件监听、断开观察者,否则会内存泄漏。
记住一个铁律:你在 mounted 里 addEventListener 了什么,就要在 unmounted 里 removeEventListener 掉。很多「切了几次页面页面就卡」的 bug,根源就是钩子里只加不删。理解了这套钩子,下面三个场景写起来就水到渠成。
四、场景一:自动聚焦 v-focus
第一个场景最直观——页面或弹窗打开时,让某个输入框立刻获焦,省得用户多点一下。逻辑就是元素挂载后调用 el.focus()。
<template>
<input v-focus placeholder="页面打开即聚焦" />
</template>
<script setup>
// 编程狮(www.w3cschool.cn)示例:自动聚焦指令 v-focus
const vFocus = {
mounted(el) {
el.focus()
},
}
</script>
在 <script setup> 里,变量名以 v 开头就会被自动识别成指令,所以 vFocus 对应模板里的 v-focus。元素一挂载,mounted 钩子就执行 el.focus()。注意只有当元素可见且未被 disabled 时聚焦才有效,弹窗用 v-focus 时要确保弹窗已经渲染出来。
五、场景二与三:防抖点击与图片懒加载
第二个场景是防抖点击。提交按钮被用户连点,会重复发请求。用指令把「点击后 300 毫秒内只触发一次」封起来,比在每个处理函数里写防抖清爽太多。
// 防抖点击指令:连点只触发一次(编程狮 www.w3cschool.cn 示例)
const vDebounce = {
mounted(el, binding) {
let timer = null
el.addEventListener('click', () => {
clearTimeout(timer)
timer = setTimeout(() => binding.value(), 300)
})
},
unmounted(el) {
el.removeEventListener('click', () => {})
},
}
第三个场景是图片懒加载:列表很长时,图片进入视口才去加载,首屏更快。配合 IntersectionObserver 实现最省事。
// 图片进入视口才加载(编程狮 www.w3cschool.cn 示例)
const vLazy = {
mounted(el, binding) {
const io = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
el.src = binding.value
io.disconnect()
}
})
io.observe(el)
},
}
做中后台界面时,这两个指令和 UI 库配合很常见,想看组件级用法可以翻 Vue Elementplus 教程,把指令嵌进表格和表单里能省不少重复代码。

总结
回到开头:Vue 自定义指令怎么用?记住它的定位——专门封装「复用一段操作 DOM 的逻辑」,能用组件或组合式函数就别上指令。落地时先分清局部与全局:只在一处用就局部,多处复用才全局;钩子用 Vue3 的 mounted、updated、unmounted,并在 unmounted 里清理监听防内存泄漏。自动聚焦、防抖点击、图片懒加载三个场景覆盖了大部分日常需求,照着上面的代码改改参数就能用。下一步建议你挑一个自己项目里的重复 DOM 操作,试着抽成第一个指令练手。
延伸学习
想把这块知识系统补齐,可以按这个顺序来:
- 先跟着 Vue3 电商后台实战课程 在真实项目里用一遍指令,比纯看文档印象深;
- 想深入钩子细节与坑,这篇 Vue3 自定义指令详解笔记 把每个生命周期讲得很透,和本文互补;
- 还在 Vue 和 React 之间犹豫的话,Vue 与 React 选择笔记 给你三条实用选型建议,帮你先定方向再深入。
常见问题
Q:自定义指令和组合式函数该选哪个?
优先组合式函数,它能返回响应式状态、更易测试;只有逻辑必须直接操作 DOM 元素(聚焦、监听、绑定第三方库)时,才用自定义指令更顺手。
Q:Vue2 的 bind 钩子在 Vue3 还能用吗?
不能。Vue3 把 bind、inserted 改名成了 mounted,update 保留但语义调整,unbind 改成了 unmounted。照着 Vue2 老写法在 Vue3 会不生效,需按新钩子名改写。
Q:全局指令注册太多会有问题吗?
功能上不会报错,但会拖慢可维护性,时间久了难判断谁还在用。建议只把真正全站复用的指令全局注册,其余留在组件内做局部指令。

免费 AI IDE



