自定义指令 #
一、指令简介 #
自定义指令用于直接操作DOM,当内置指令无法满足需求时可以使用自定义指令。
1.1 注册方式 #
javascript
// 全局注册
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 局部注册
export default {
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
1.2 使用指令 #
vue
<template>
<input v-focus>
</template>
二、指令钩子函数 #
2.1 钩子列表 #
javascript
const myDirective = {
// 绑定元素的父组件挂载前
created(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件挂载前
beforeMount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件挂载时
mounted(el, binding, vnode, prevVnode) {},
// 父组件更新前
beforeUpdate(el, binding, vnode, prevVnode) {},
// 父组件更新后
updated(el, binding, vnode, prevVnode) {},
// 父组件卸载前
beforeUnmount(el, binding, vnode, prevVnode) {},
// 父组件卸载后
unmounted(el, binding, vnode, prevVnode) {}
}
2.2 钩子参数 #
javascript
app.directive('demo', {
mounted(el, binding, vnode) {
// el: 绑定的元素
console.log(el)
// binding: 绑定对象
console.log(binding.value) // 指令值
console.log(binding.oldValue) // 之前的值
console.log(binding.arg) // 指令参数 v-demo:foo
console.log(binding.modifiers) // 修饰符对象 { foo: true }
console.log(binding.instance) // 组件实例
console.log(binding.dir) // 指令定义对象
// vnode: 虚拟节点
console.log(vnode)
}
})
三、常用自定义指令 #
3.1 自动聚焦 #
javascript
// directives/focus.js
export const vFocus = {
mounted(el) {
el.focus()
}
}
vue
<template>
<input v-focus>
</template>
<script setup>
import { vFocus } from './directives/focus'
</script>
3.2 防抖指令 #
javascript
// directives/debounce.js
export const vDebounce = {
mounted(el, binding) {
const { value, arg = 'click' } = binding
const delay = arg ? parseInt(arg) : 300
let timer = null
el.addEventListener('click', () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
value()
}, delay)
})
},
unmounted(el) {
el.removeEventListener('click')
}
}
vue
<template>
<button v-debounce="handleClick">防抖按钮</button>
<button v-debounce:500="handleClick">500ms防抖</button>
</template>
3.3 权限指令 #
javascript
// directives/permission.js
export const vPermission = {
mounted(el, binding) {
const { value } = binding
const permissions = JSON.parse(localStorage.getItem('permissions') || '[]')
if (!permissions.includes(value)) {
el.parentNode?.removeChild(el)
}
}
}
vue
<template>
<button v-permission="'admin'">管理员可见</button>
<button v-permission="'editor'">编辑可见</button>
</template>
3.4 点击外部指令 #
javascript
// directives/clickOutside.js
export const vClickOutside = {
mounted(el, binding) {
el._clickOutside = (event) => {
if (!el.contains(event.target)) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
}
vue
<template>
<div v-click-outside="handleClickOutside">
点击外部会触发
</div>
</template>
3.5 复制指令 #
javascript
// directives/copy.js
export const vCopy = {
mounted(el, binding) {
el._copyValue = binding.value
el.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(el._copyValue)
console.log('复制成功')
} catch (err) {
console.error('复制失败:', err)
}
})
},
updated(el, binding) {
el._copyValue = binding.value
}
}
vue
<template>
<button v-copy="textToCopy">复制文本</button>
</template>
3.6 懒加载指令 #
javascript
// directives/lazy.js
export const vLazy = {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
})
observer.observe(el)
el._observer = observer
},
unmounted(el) {
if (el._observer) {
el._observer.unobserve(el)
}
}
}
vue
<template>
<img v-lazy="imageUrl" alt="懒加载图片">
</template>
3.7 长按指令 #
javascript
// directives/longpress.js
export const vLongpress = {
mounted(el, binding) {
const { value, arg } = binding
const duration = arg ? parseInt(arg) : 500
let timer = null
const start = (e) => {
if (e.type === 'click' && e.button !== 0) return
if (timer === null) {
timer = setTimeout(() => {
value()
}, duration)
}
}
const cancel = () => {
if (timer !== null) {
clearTimeout(timer)
timer = null
}
}
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._cleanup = () => {
el.removeEventListener('mousedown', start)
el.removeEventListener('touchstart', start)
el.removeEventListener('click', cancel)
el.removeEventListener('mouseout', cancel)
el.removeEventListener('touchend', cancel)
el.removeEventListener('touchcancel', cancel)
}
},
unmounted(el) {
el._cleanup?.()
}
}
vue
<template>
<button v-longpress="handleLongPress">长按触发</button>
<button v-longpress:1000="handleLongPress">长按1秒触发</button>
</template>
3.8 工具提示指令 #
javascript
// directives/tooltip.js
export const vTooltip = {
mounted(el, binding) {
const tooltip = document.createElement('div')
tooltip.className = 'tooltip'
tooltip.textContent = binding.value
tooltip.style.cssText = `
position: absolute;
background: #333;
color: #fff;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000;
display: none;
`
document.body.appendChild(tooltip)
const show = () => {
const rect = el.getBoundingClientRect()
tooltip.style.display = 'block'
tooltip.style.left = rect.left + 'px'
tooltip.style.top = rect.bottom + 5 + 'px'
}
const hide = () => {
tooltip.style.display = 'none'
}
el.addEventListener('mouseenter', show)
el.addEventListener('mouseleave', hide)
el._tooltip = tooltip
el._showTooltip = show
el._hideTooltip = hide
},
updated(el, binding) {
if (el._tooltip) {
el._tooltip.textContent = binding.value
}
},
unmounted(el) {
if (el._tooltip) {
el._tooltip.remove()
}
el.removeEventListener('mouseenter', el._showTooltip)
el.removeEventListener('mouseleave', el._hideTooltip)
}
}
vue
<template>
<button v-tooltip="提示文本">悬停显示提示</button>
</template>
四、函数简写 #
4.1 简写形式 #
javascript
// 只在mounted和updated时触发
app.directive('color', (el, binding) => {
el.style.color = binding.value
})
vue
<template>
<p v-color="textColor">彩色文字</p>
</template>
4.2 对象字面量 #
javascript
app.directive('style', (el, binding) => {
Object.assign(el.style, binding.value)
})
vue
<template>
<div v-style="{ color: 'red', fontSize: '20px' }">
样式文字
</div>
</template>
五、指令组合式函数 #
javascript
// composables/useDirective.js
import { onMounted, onUnmounted } from 'vue'
export function useClickOutside(elementRef, callback) {
const handleClick = (event) => {
if (elementRef.value && !elementRef.value.contains(event.target)) {
callback(event)
}
}
onMounted(() => {
document.addEventListener('click', handleClick)
})
onUnmounted(() => {
document.removeEventListener('click', handleClick)
})
}
vue
<script setup>
import { ref } from 'vue'
import { useClickOutside } from './composables/useDirective'
const dropdownRef = ref(null)
const isOpen = ref(false)
useClickOutside(dropdownRef, () => {
isOpen.value = false
})
</script>
六、总结 #
指令钩子 #
| 钩子 | 触发时机 |
|---|---|
| created | 绑定元素的父组件挂载前 |
| beforeMount | 绑定元素的父组件挂载前 |
| mounted | 绑定元素的父组件挂载时 |
| beforeUpdate | 父组件更新前 |
| updated | 父组件更新后 |
| beforeUnmount | 父组件卸载前 |
| unmounted | 父组件卸载后 |
自定义指令要点:
- 用于直接操作DOM
- 注意在unmounted中清理事件监听
- 使用binding获取指令参数和值
- 复杂逻辑考虑使用组合式函数
最后更新:2026-03-26