要自定义指令,那DOM元素要自己操作了;
某种程度上说,自定义指令就是把原生操作DOM做了一次封装;
例如:自定义 v-hello 指令,要做4次DOM操作,封装后就可以复用,不必重写了;
自定义指令_函数式
Vue 引入一个新的配置对象: directives:{}
来放自定义指令;
需求1:定义一个 v-big 指令,和 v-text 功能类似,但会把绑定的数值放大 10倍;
<div id="root">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
</div>
new Vue({
el: '#root',
data: {
n: 1,
},
directives: {
big(element, binding) { // 定义这个新指定,定义不用加 v-
console.log(element, binding)
element.innerText = binding.value * 10
}
}
})
提示1:自定义指令在定义时,不用加 “v” ,在标签上使用指令的时候,要加上 "v" ;
提示2:自定义指令的函数,可不能一上来就写 return ,而是使用 Vue 提供的两个参数来操作DOM;
提示3:element 顾名思义,就是当前要操作的DOM元素对象 <span>
提示4:binding 是一个存储着当前元素信息的对象,具体是哪些信息呢?指令名、指令的绑定值,指令的表达式等;
图中看出:
element :就是 <span> 元素对象;
binding :就是 v-big 和 元素对象 <span>之间的绑定信息:
expression 是表达式 n ,v-big="n"
,中的 n ;
rawName 是指令名;
value:指令绑定的值;
这两个参数是哪儿来的? 是Vue内置好给我们用的,有了这两个参数,再操作当前DOM是不是就更轻松了?
element.innerText = binding.value * 10
这段代码的意思就是,从 binding 中把指令绑定的值放大10倍,然后让 element 放到 innerText 上去,从而实现 v-big 指令的效果,并且实现了响应式;
注意:big() 这种写成函数的形式,会在两个时间点执行:
1)指令与元素成功绑定时(一上来);
2)指令所在模拟被重新解析时;
下面会演示一种在模板被加载到页面时的情况;
自定义指令_对象式
需求2:定一个 v-fbind 指令,和 v-bind 功能类似,但可以让其所绑定的 input 元素默认获取焦点;
<div id="root">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr>
<input type="text" v-fbind:value="n">
</div>
提示:绑定自定义指令的方式没有问题,插值没有问题,但是获取焦点上,会有点问题
new Vue({
el: '#root',
data: {
n: 1,
},
directives: {
big(element, binding) {
console.log(element, binding)
element.innerText = binding.value * 10
},
fbind(element,binding){
element.value = binding.value
element.focus()
}
}
})
看下效果,页面打开,在没点击按钮之前,input 并没有获得焦点;
不是代码写错了,而是执行的时机错了,页面打开后,Vue在建立绑定的时候,元素还没渲染到页面上,代码就已经执行完了,等元素上到页面时,要执行的代码已经过去了,效果自然就没有了;
自定义指令调用的过程中,要经历几个步骤:
1)指令与元素成功绑定(bind):仅代表在内存中建立了关系,元素还没有被渲染到页面上;
2)指令所在元素被插入页面时(inserted)
3)指令所在的模板被重新解析(update)
了解完这几个步骤,代码就要写在正确的时间点上,Vue已经为这几个关键时间点内置了函数,我们只需在时间点函数中写相应代码就行,这3个时间点函数分别是:bin()
,inserted()
,update()
;
代码改一下:
new Vue({
el: '#root',
data: {
n: 1,
},
directives: {
big(element, binding) {
// console.log(element, binding)
element.innerText = binding.value * 10
},
fbind: {
// 指令与元素成功绑定时
bind(element, binding) {
console.log('bind');
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding) {
console.log('inserted');
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
console.log('update');
element.value = binding.value
// element.focus() 还可以让他继续获取焦点
}
}
}
})
提示:1建立绑定、2插入元素、3模板重解析,这3个时间点各自有代码,在错误的时间点上执行再正确的代码都可能没有效果;
提示:本例把这3个时间点执行时都log出来了,发现,bind 和 inserted 只会执行一次,update 可能会被执行多次;
代码并不复杂,思考在什么时间点执行什么语句还需要经验积累;
注意:这种固定时间点的函数叫钩子函数,一般语言都有,函数名是固定的,传入的参数就叫钩子函数参数,共有四种:elememt,binding,vnode和oldVnode;
自定义指令总结
多个单词的指令
如果指令需要多少单词的组合,就使用 '-' 连接:
<span v-big-number="n"></span>
在配置对象中,还要用引号引起来:
directives: {
'big-number': {
bind() { },
inserted() { },
update() { }
},
'my-define-directive'() {}
}
函数式还是对象式
至于自定义指令使用函数式(简写)还是对象式(完整写法),就需要判断代码中是否要在 inserted 时间点执行某个功能了;
自定义指令函数中的this
// 定义局部自定义指令
directives: {
big(element, binding) {
console.log('big', this)
element.innerText = binding.value * 10
}
}
注意:这里的 this 是指 window 的,并不是指向 vm 或 元素对象的;
全局自定义指令
和全局过滤器的写法类似,把全局自定义指令写在 Vue.directive('xxx',function(){...})
// 定义全局指令
Vue.directive('fbind', {
// 指令与元素成功绑定时
bind(element, binding) {
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
element.focus()
}
});
Vue.directive('big',(element,binding)=>{
element.innerText = binding.value
});
【自定义指令】总结
1)定义语法:局部指令
对象式(完整写法)
new Vue({
directives:{指令名:{配置对象(3个时间点函数)}}
})
函数式(简化写法)
new Vue({
directives:{指令名:回调函数}
})
2)定义语法:全局指令
对象式(完整写法)
Vue.directive('指令名',{
配置对象(3个时间点函数)
})
函数式(简化写法)
Vue.directive('指令名',回调函数)
3)配置对象中常用的3个回调:
1、bind :指令与元素成功绑定时调用 ;
2、inserted :指令所在元素被插入页面时调用;
3、update :指令所在模板结构被重新解析时调用;
备注:
1)指令定义时不加 “v-”,但使用时要加 “v-” ;
2)指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase命名;