Vue 3 自定义指令 + 完整生命周期详解(2025–2026 实用版)
Vue 3 的自定义指令和组件生命周期是中高级开发中非常高频的内容,尤其在以下场景中几乎必考/必用:
- 复杂表单(自动聚焦、输入防抖、权限显隐)
- 拖拽、长按、水印、懒加载图片
- 第三方库集成(图表 resize、虚拟滚动优化)
- 调试/埋点/权限控制指令
下面把 自定义指令 和 组件生命周期 放在一起讲解,因为它们在实际项目中经常结合使用。
一、Vue 3 组件生命周期(Composition API 版为主)
Vue 3 Composition API 使用 onXXX 钩子函数,Options API 则使用 beforeCreate 等选项。
| 阶段 | Options API | Composition API | 执行时机(最常用场景) | 是否异步 |
|---|---|---|---|---|
| beforeCreate | beforeCreate | —(不推荐) | 实例初始化之前,几乎不用 | 同步 |
| created | created | —(不推荐) | 数据响应式处理完成,可访问 data / props | 同步 |
| beforeMount | beforeMount | onBeforeMount | 模板编译完成,即将挂载到 DOM 前 | 同步 |
| mounted | mounted | onMounted | 组件挂载完成,DOM 已渲染,可操作真实 DOM | 同步 |
| beforeUpdate | beforeUpdate | onBeforeUpdate | 数据变化 → 重新渲染前(可获取旧 DOM 状态) | 同步 |
| updated | updated | onUpdated | DOM 更新完成(注意:不要在这里修改响应式数据) | 同步 |
| beforeUnmount | beforeUnmount | onBeforeUnmount | 组件即将卸载(清理定时器、事件监听、订阅等) | 同步 |
| unmounted | unmounted | onUnmounted | 组件完全卸载,DOM 已移除 | 同步 |
| activated | activated | onActivated | keep-alive 组件被激活时 | 同步 |
| deactivated | deactivated | onDeactivated | keep-alive 组件失活时 | 同步 |
| errorCaptured | errorCaptured | onErrorCaptured | 捕获子组件错误(可返回 false 阻止抛出) | 同步 |
| renderTracked | — | onRenderTracked | 响应式依赖被追踪(调试用) | — |
| renderTriggered | — | onRenderTriggered | 响应式依赖被触发重新渲染(调试用) | — |
| serverPrefetch | — | onServerPrefetch | SSR 预取数据(Nuxt / Vite SSR 常用) | — |
最常用的 5 个钩子(日常开发占比 90%+):
onMounted→ 获取 DOM、发起首次请求、初始化第三方库onBeforeUnmount/onUnmounted→ 清理定时器、事件监听、WebSocket 断开onUpdated→ DOM 更新后做一些微调(慎用,避免死循环)onActivated/onDeactivated→ keep-alive 场景(列表缓存、音频播放暂停)onErrorCaptured→ 组件级错误边界
代码示例(组合式 API 完整写法)
<script setup>
import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
const count = ref(0)
let timer = null
onMounted(() => {
console.log('组件已挂载,可以操作 DOM')
timer = setInterval(() => count.value++, 1000)
})
onBeforeUnmount(() => {
console.log('组件即将卸载,清理资源')
clearInterval(timer)
})
onActivated(() => {
console.log('keep-alive 组件被激活')
// 可重新启动轮询、视频播放等
})
onDeactivated(() => {
console.log('keep-alive 组件失活')
// 暂停轮询、视频等
})
</script>
二、Vue 3 自定义指令(v-xxx)
Vue 3 自定义指令的钩子函数比 Vue 2 精简了很多,统一成了 6 个生命周期:
| 钩子名称 | Options API 写法 | Composition API 写法(directive) | 执行时机 | 是否传入 el |
|---|---|---|---|---|
| created | created | created | 指令绑定到元素时,在 beforeMount 之前 | 是 |
| beforeMount | beforeMount | beforeMount | 元素挂载前 | 是 |
| mounted | mounted | mounted | 元素挂载完成后(最常用) | 是 |
| beforeUpdate | beforeUpdate | beforeUpdate | 包含组件的 VNode 更新前 | 是 |
| updated | updated | updated | 包含组件的 VNode 更新后 | 是 |
| beforeUnmount | beforeUnmount | beforeUnmount | 元素卸载前 | 是 |
| unmounted | unmounted | unmounted | 元素卸载后(最常用于清理) | 是 |
最常用的是:mounted + unmounted
三、自定义指令完整写法对比(三种方式)
方式1:全局注册(main.js / main.ts)
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 简单示例:v-focus 自动聚焦
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 带清理的示例:v-longpress 长按 500ms 触发
app.directive('longpress', {
mounted(el, binding) {
let timer = null
const start = (e) => {
if (e.type === 'click' && e.button !== 0) return
timer = setTimeout(() => {
binding.value(e) // 执行绑定的函数
}, 500)
}
const cancel = () => clearTimeout(timer)
el.addEventListener('mousedown', start)
el.addEventListener('touchstart', start)
el.addEventListener('click', cancel)
el.addEventListener('mouseout', cancel)
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
// 推荐:把清理函数存到 el 上
el._longpressCleanup = () => {
el.removeEventListener('mousedown', start)
// ... 移除所有监听
clearTimeout(timer)
}
},
beforeUnmount(el) {
// 组件卸载时自动清理
if (el._longpressCleanup) {
el._longpressCleanup()
}
}
})
app.mount('#app')
方式2:局部注册(<script setup> 中使用)
<script setup>
import { directive } from 'vue'
const vPermission = {
mounted(el, binding) {
if (!hasPermission(binding.value)) {
el.parentNode?.removeChild(el)
}
}
}
// 使用方式
defineOptions({
directives: { permission: vPermission }
})
</script>
<template>
<button v-permission="'user:edit'">编辑</button>
</template>
方式3:最推荐 —— 使用 composable 创建可复用指令
// composables/useFocus.js
import { onMounted, onBeforeUnmount } from 'vue'
export function useFocus() {
const focusDirective = {
mounted(el) {
el.focus()
}
}
return { focusDirective }
}
<script setup>
import { useFocus } from '@/composables/useFocus'
const { focusDirective } = useFocus()
</script>
<template>
<input v-focus />
</template>
四、经典自定义指令合集(面试 + 项目必备)
| 指令名称 | 功能 | 常用钩子 | 典型实现要点 |
|---|---|---|---|
| v-focus | 自动聚焦 | mounted | el.focus() |
| v-copy | 一键复制内容 | mounted | document.execCommand |
| v-lazy-img | 图片懒加载 | mounted + IntersectionObserver | |
| v-longpress | 长按触发 | mounted + 定时器清理 | |
| v-permission | 按钮级权限控制 | mounted | 判断权限 → remove 或 display:none |
| v-debounce | 输入防抖 | beforeUpdate + updated | |
| v-resize | 监听元素尺寸变化 | mounted | ResizeObserver |
| v-watermark | 页面/元素水印 | mounted | canvas 或 div 叠加 |
| v-draggable | 拖拽移动 | mounted + mousemove | |
| v-loading | 自定义 loading 遮罩 | mounted / updated |
五、总结:面试/项目常问的 10 个问题
- Vue 3 自定义指令比 Vue 2 少了哪些钩子?为什么?
- 自定义指令的
el、binding、vnode、prevNode分别是什么? - 为什么推荐在
beforeUnmount或unmounted做清理? - 如何让自定义指令支持修饰符(.prevent .stop 等)?
v-model在自定义组件和自定义指令中的区别?- 组件的
onBeforeUnmount和指令的beforeUnmount执行顺序? - 如何调试自定义指令(Vue Devtools 是否支持)?
- 自定义指令如何获取组件实例(getCurrentInstance)?
- 为什么很多 UI 库(如 Element Plus)大量使用自定义指令?
- 你写过哪些自定义指令?解决了什么痛点?
有具体想实现的自定义指令(比如 v-permission、v-lazy、v-draggable)需要完整代码示例吗?
或者想看某个生命周期钩子在真实业务中的典型用法?可以直接告诉我~