Vue 允许开发者注册自定义指令,用于对普通 DOM 元素进行底层操作。以下是关于 Vue 自定义指令的详细指南。
1. 注册自定义指令
1.1 全局注册
在 main.js
或入口文件中:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时...
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
1.2 局部注册
在组件选项中:
export default {
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
}
2. 指令钩子函数
一个指令定义对象可以提供以下几个钩子函数(均为可选):
-
bind
:只调用一次,指令第一次绑定到元素时调用 -
inserted
:被绑定元素插入父节点时调用 -
update
:所在组件的 VNode 更新时调用,但可能发生在其子 VNode 更新之前 -
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用 -
unbind
:只调用一次,指令与元素解绑时调用
Vue.directive('demo', {
bind(el, binding, vnode) {
// 初始化设置
},
inserted(el, binding, vnode) {
// 元素插入父节点时调用
},
update(el, binding, vnode, oldVnode) {
// 组件更新时调用
},
componentUpdated(el, binding, vnode, oldVnode) {
// 组件和子组件更新后调用
},
unbind(el, binding, vnode) {
// 清理工作
}
})
3. 钩子函数参数
每个钩子函数都接收以下参数:
-
el
:指令所绑定的元素,可以用来直接操作 DOM -
binding
:一个对象,包含以下属性:-
name
:指令名,不包括v-
前缀 -
value
:指令的绑定值 -
oldValue
:指令绑定的前一个值(仅在update
和componentUpdated
中可用) -
expression
:字符串形式的指令表达式 -
arg
:传给指令的参数 -
modifiers
:一个包含修饰符的对象
-
-
vnode
:Vue 编译生成的虚拟节点 -
oldVnode
:上一个虚拟节点(仅在update
和componentUpdated
钩子中可用)
4. 使用示例
4.1 基础示例
Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
})
使用:
<p v-color="'red'">这段文字会是红色的</p>
<p v-color="colorValue">这段文字颜色由数据决定</p>
4.2 带参数和修饰符的指令
Vue.directive('pin', {
bind(el, binding) {
el.style.position = 'fixed'
const s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'
if (binding.modifiers.animate) {
el.style.transition = `${s} 0.5s`
}
}
})
使用:
<div v-pin:left="200">固定在左边200px处</div>
<div v-pin:top.animate="100">固定在顶部100px处,带动画效果</div>
4.3 动态指令参数
<div v-pin:[direction]="200">固定位置</div>
export default {
data() {
return {
direction: 'top'
}
}
}
5. 实用指令示例
5.1 权限控制指令
Vue.directive('permission', {
inserted(el, binding) {
const { value } = binding
const permissions = ['edit', 'delete'] // 实际应从store或API获取
if (value && !permissions.includes(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
})
使用:
<button v-permission="'edit'">编辑</button>
5.2 防抖指令
Vue.directive('debounce', {
inserted(el, binding) {
let timer
el.addEventListener('input', () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
binding.value()
}, 500)
})
}
})
使用:
<input v-debounce="search" placeholder="输入搜索内容">
5.3 图片懒加载
Vue.directive('lazy', {
inserted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
})
observer.observe(el)
}
})
使用:
<img v-lazy="'path/to/image.jpg'" alt="图片">
5.4 复制指令
Vue.directive('copy', {
bind(el, { value }) {
el.$value = value
el.handler = () => {
if (!el.$value) {
console.log('无复制内容')
return
}
const textarea = document.createElement('textarea')
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
textarea.value = el.$value
document.body.appendChild(textarea)
textarea.select()
const result = document.execCommand('Copy')
if (result) {
console.log('复制成功')
}
document.body.removeChild(textarea)
}
el.addEventListener('click', el.handler)
},
componentUpdated(el, { value }) {
el.$value = value
},
unbind(el) {
el.removeEventListener('click', el.handler)
}
})
使用:
<button v-copy="text">复制</button>
6. 最佳实践
-
命名规范:使用小写字母和连字符命名指令(如
v-my-directive
) -
避免过度使用:优先使用组件和 props 解决问题,指令适用于底层 DOM 操作
-
清理工作:在
unbind
钩子中移除事件监听器和定时器 -
性能考虑:避免在
update
钩子中进行昂贵的操作 -
复用性:将通用指令提取为全局指令
-
文档化:为自定义指令编写文档说明其用途和用法
自定义指令是 Vue 强大的功能之一,合理使用可以极大地提高代码复用性和开发效率。