核心概念
自定义指令用于封装底层 DOM 操作,主要应用场景:
- 直接操作 DOM(聚焦、滚动等)
- 集成第三方 DOM 库
- 添加特殊行为(权限控制、拖拽等)
一、指令生命周期钩子(7个)
javascript
const myDirective = {
// 元素属性/事件监听应用前调用
created(el, binding, vnode) {},
// 元素插入父节点前调用
beforeMount(el, binding, vnode) {},
// 元素挂载后调用(最常用)
mounted(el, binding, vnode) {},
// 父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 父组件及子组件更新后调用
updated(el, binding, vnode, prevVnode) {},
// 父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 指令解绑且父组件卸载后调用
unmounted(el, binding, vnode) {}
}
二、钩子函数参数详解
-
el
: 指令绑定的 DOM 元素 -
binding
: 包含以下属性的对象javascript{ value: 当前指令值(v-my-dir="value"), oldValue: 旧值(仅 beforeUpdate/updated 可用), arg: 参数(v-my-dir:arg), modifiers: 修饰符对象(v-my-dir.mod1.mod2 → { mod1: true, mod2: true }), instance: 组件实例, dir: 指令定义对象 }
-
vnode
: 当前虚拟节点 -
prevVnode
: 更新前的虚拟节点(仅 beforeUpdate/updated)
三、注册方式
1. 全局注册
javascript
// main.js
import { createApp } from 'vue'
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus()
}
})
2. 局部注册
javascript
// Component.vue
export default {
directives: {
highlight: {
mounted(el, binding) {
el.style.backgroundColor = binding.value || 'yellow'
}
}
}
}
四、使用示例
1. 基础用法
html
<input v-focus>
<button v-permission="'admin'">管理员按钮</button>
2. 带参数
html
<div v-pin:top="50">固定在顶部50px处</div>
javascript
app.directive('pin', {
mounted(el, binding) {
el.style.position = 'fixed'
el.style[binding.arg || 'top'] = `${binding.value}px`
}
})
3. 带修饰符
html
<button v-click.once="log">仅触发一次</button>
javascript
app.directive('click', {
mounted(el, binding) {
const handler = binding.value
let clicked = false
el.addEventListener('click', () => {
if (binding.modifiers.once && clicked) return
handler()
clicked = true
})
}
})
五、完整功能示例
1. 权限控制指令
javascript
app.directive('permission', {
mounted(el, binding) {
const { value, modifiers } = binding
const userRoles = ['admin', 'editor'] // 实际从store获取
if (!value.some(role => userRoles.includes(role))) {
if (modifiers.hide) {
el.style.display = 'none'
} else {
el.disabled = true
el.title = '无权限操作'
}
}
}
})
html
<button v-permission="['admin']">管理员操作</button>
<button v-permission.hide="['super-admin']">超级管理员操作</button>
2. 拖拽指令
javascript
app.directive('drag', {
mounted(el) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0
const dragMouseDown = (e) => {
e.preventDefault()
pos3 = e.clientX
pos4 = e.clientY
document.onmouseup = closeDragElement
document.onmousemove = elementDrag
}
const elementDrag = (e) => {
e.preventDefault()
pos1 = pos3 - e.clientX
pos2 = pos4 - e.clientY
pos3 = e.clientX
pos4 = e.clientY
el.style.top = (el.offsetTop - pos2) + "px"
el.style.left = (el.offsetLeft - pos1) + "px"
}
const closeDragElement = () => {
document.onmouseup = null
document.onmousemove = null
}
el.onmousedown = dragMouseDown
}
})
html
<div v-drag style="position:absolute">可拖拽元素</div>
3. 防抖指令
javascript
app.directive('debounce', {
mounted(el, binding) {
const [callback, delay = 300] = binding.value
let timer = null
el.addEventListener('click', () => {
clearTimeout(timer)
timer = setTimeout(() => callback(), delay)
})
}
})
html
<button v-debounce="[submit, 500]">防抖提交</button>
六、最佳实践
-
优先使用组件:非 DOM 操作优先考虑组件
-
解绑事件 :在
unmounted
中移除事件监听javascriptunmounted(el) { el.removeEventListener('click', el._clickHandler) }
-
动态指令参数 :
html<div v-move:[direction]="100"></div>
-
指令复用:通过参数化实现通用指令
-
性能优化 :避免在
updated
中执行重操作
七、注意事项
- 组件可能使用
inheritAttrs: false
影响指令 - 指令优先级高于组件生命周期
- 在 SSR 中避免 DOM 操作
- 动态参数变化会触发
beforeUpdate/updated