自定义指令提供了多个钩子函数,让你可以在指令生命周期的不同阶段执行操作。理解每个钩子的调用时机是编写高质量指令的关键。
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
// 指令首次绑定到元素时调用
},
inserted(el, binding, vnode, oldVnode) {
// 被绑定元素插入父节点时调用
},
update(el, binding, vnode, oldVnode) {
// 所在组件的 VNode 更新时调用
},
componentUpdated(el, binding, vnode, oldVnode) {
// 指令所在组件的 VNode 及其子 VNode 全部更新后调用
},
unbind(el, binding, vnode, oldVnode) {
// 指令与元素解绑时调用
}
})
调用时机:指令首次绑定到元素时,只调用一次。
适用场景:
Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
}
})
注意:此时元素可能还未插入 DOM,父节点可能不存在。
调用时机:被绑定元素插入父节点时调用(父节点存在即可,不必已在文档中)。
适用场景:
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
为什么 focus 要在 inserted 而不是 bind?
因为 focus 需要元素在 DOM 中才能生效,bind 时元素可能还未渲染。
调用时机:所在组件的 VNode 更新时调用,可能发生在其子 VNode 更新之前。
适用场景:
Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
if (binding.value !== binding.oldValue) {
el.style.color = binding.value
}
}
})
调用时机:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
适用场景:
Vue.directive('scroll-to-bottom', {
componentUpdated(el) {
el.scrollTop = el.scrollHeight
}
})
调用时机:指令与元素解绑时调用,只调用一次。
适用场景:
Vue.directive('click-outside', {
bind(el, binding) {
el._clickOutside = function(event) {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unbind(el) {
document.removeEventListener('click', el._clickOutside)
delete el._clickOutside
}
})
以 v-if 控制的元素为例:
v-if="true" 时:
1. bind
2. inserted
v-if="false" 时:
1. unbind
再次 v-if="true" 时:
1. bind
2. inserted
<div id="app">
<button @click="show = !show">切换</button>
<button @click="color = color === 'red' ? 'blue' : 'red'">改变颜色</button>
<p v-if="show" v-demo="color">测试文本</p>
</div>
<script>
Vue.directive('demo', {
bind(el, binding) {
console.log('bind')
el.style.color = binding.value
},
inserted(el, binding) {
console.log('inserted')
},
update(el, binding) {
console.log('update', binding.value, binding.oldValue)
el.style.color = binding.value
},
componentUpdated(el, binding) {
console.log('componentUpdated')
},
unbind(el) {
console.log('unbind')
}
})
new Vue({
el: '#app',
data: {
show: true,
color: 'red'
}
})
</script>
如果 bind 和 update 需要相同逻辑:
Vue.directive('color', function(el, binding) {
el.style.color = binding.value
})
等价于:
Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
})
Vue.directive('debounce', {
bind(el, binding) {
let timer = null
el._debounce = function(event) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
binding.value(event)
}, binding.arg || 300)
}
el.addEventListener('input', el._debounce)
},
unbind(el) {
el.removeEventListener('input', el._debounce)
delete el._debounce
}
})
使用:
<input v-debounce:500="search" placeholder="搜索...">
Vue.directive('lazy', {
bind(el, binding) {
el._observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value
el._observer.unobserve(el)
}
})
})
el._observer.observe(el)
},
unbind(el) {
if (el._observer) {
el._observer.disconnect()
delete el._observer
}
}
})
| 需求 | 推荐钩子 |
|---|---|
| 初始化样式/属性 | bind |
| 操作 DOM(focus、尺寸) | inserted |
| 响应数据变化 | update |
| 等待子组件更新 | componentUpdated |
| 清理资源 | unbind |
理解钩子函数后,接下来学习钩子参数,获取指令的详细信息。