除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
注意:这里官方对自定义指令有明确的说明,所以我们应该尽量去遵守,而不应该盲目的滥用自定义指令去完成一些任务
Vue为我们添加了很多内置指令,我们可以直接使用,例如v-on,v-bind,v-model,形如v-xx。
假设我们现在需要在进入一个页面后,使页面的一个输入框自动获得焦点:
<template>
<div>
<input type="text" placeholder="这里没有使用自定义指令"></input>
<input v-focus type="text" placeholder="这里使用了自定义指令"></input>
</div>
</template>
我们可以在全局或局部定义这个自定义指令:
Vue.directive('focus', {
inserted: function (el) {
el.focus()
}
})
组将可以接受一个directives
选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
自定义指令的钩子也就是一个自定义指令对象的生命周期函数。
官方文档中说明:
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind
:只调用一次,指令与元素解绑时调用。
为了加深对这几个钩子函数的理解,我们测试一个小demo:
<template>
<div>
<input v-check type="text" placeholder="这里使用了自定义指令" v-model="value" v-if="v_if" v-show="v_show"></input>
<button @click="changeValue">changeValue</button>
<button @click="changeShow">changeShow</button>
<button @click="changeIf">changeIf</button>
</div>
</template>
<script>
export default {
name: 'app',
directives:{
check: {
bind: function () {
console.log('bind')
},
inserted: function () {
console.log('inserted')
},
update: function () {
console.log('update')
},
componentUpdated: function () {
console.log('componentUpdated')
},
unbind: function () {
console.log('unbind')
}
}
},
methods:{
changeValue () {
this.value = 'changed'
},
changeShow () {
this.v_show = !this.v_show
},
changeIf () {
this.v_if = !this.v_if
}
},
data () {
return {
value: '',
v_if: true,
v_show: true
}
},
当页面被加载出来时,控制台输出了:
当我们点击changeValue按钮之后,控制台输出了:
当我们点击changeShow按钮之后,控制台输出了:
之所以changeValue和changeShow会在控制台显示相同的内容,是因为这两个触发事件都改变了组件的视图,视图更新之前触发了update钩子,更新完成之后触发了componentUpdated钩子。
当我们点击changeIf按钮之后,控制台输出了:
指令钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作 DOM。
binding
:一个对象,包含以下 property:
name :指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode
:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
假设我们现在存在一个这样的自定义指令:
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '
' +
'value: ' + s(binding.value) + '
' +
'expression: ' + s(binding.expression) + '
' +
'argument: ' + s(binding.arg) + '
' +
'modifiers: ' + s(binding.modifiers) + '
' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
模板:
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
注意:
除 update 与 componentUpdated 钩子函数之外,每个钩子函数都只含有 el、binding、vnode 这三个参数
在每个函数中,第一个参数永远是 el, 表示被绑定了指令的那个 dom 元素,这个el 参数,是一个原生的 JS 对象,所以 Vue 自定义指令可以用来直接和 DOM 打交道
binding 是一个对象,它包含以下属性:name、value、oldValue、expression、arg、modifiers
oldVnode 只有在 update 与 componentUpdated 钩子中生效
除了 el 之外,binding、vnode 属性都是只读的
这里我们使用的防抖节流函数来自笔者的另一篇博客:JavaScript 防抖(debounce) 和 节流(throttling),如果有疑问,可以查看该博客加深理解。
util/util.js:
function debounce(fn,delay,immediate=false){
let timer = null
let triggerTime // 触发fn的时间
let run = (wait) => {
console.log('create timer')
timer = setTimeout(()=>{
let executeTime = (new Date()).getTime() // 执行fn的时间
let alreadyWait = executeTime - triggerTime
if (alreadyWait < wait){
run(delay - alreadyWait)
} else {
if (!immediate){
fn()
}
timer = null
}
}, wait)
}
return () => {
triggerTime = (new Date()).getTime()
if (!timer) {
if (immediate){
fn()
}
run(delay)
}
}
}
function throttling(fn,delay,immediate=false){
let timer = null
return () => {
if (!timer){
if (immediate){
fn()
}
timer = setTimeout(()=>{
if (!immediate) fn()
timer = null
},delay)
}
}
}
export { debounce,throttling }
app.vue:
<template>
<div>
<span>{{value}}</span>
<button v-optimize:debounce="{emit:'click',fn:addValue,delay:3000}">add</button>
</div>
</template>
<script>
import { debounce, throttling } from './util/util'
export default {
name: 'app',
directives:{
optimize: {
bind: function (el, binding) {
// 根据binding.value获取参数
const { emit,fn,delay=1000,immediate=false} = binding.value
if (binding.arg==='debounce'){
el.addEventListener(emit, debounce(fn,delay,immediate))
} else {
el.addEventListener(emit,throttling(fn,delay,immediate))
}
}
}
},
methods:{
addValue(){
this.value ++
}
},
data () {
return {
value: 0,
}
}
}
</script>
我们是如何向自定义指令传入参数的?v-optimize:debounce="{emit:'click',fn:addValue,delay:3000}"
,在自定义指令的钩子函数中,binding.arg
就是debounce,binding.value
就是 {emit:‘click’,fn:addValue,delay:3000} 。然后,根据自定义指令的参数,来选择防抖还是节流。