在我看来,指令的作用就是为了能够直接的来修改DOM。
一些vue内置的指令,
比如v-if v-else v-show v-bind v-html v-text 等等的指令,
或多或少都是用来直接的操作DOM,
如果你不知道什么时候使用指令?那么请记住一点。
那就是,在你需要操作DOM的时候,请选择使用指令来进行。
vue提供的,每一种工具最好就是只针对某一种行为来使用。
现在,我们就来说说指令。
一些内置的指令也就不需要说了,现在我们就直接上手自定义的指令。
首先,指令分为局部的指令和全局的指令。
局部的指令是在组件内部使用。
全局指令则是项目的任何地方都可以使用。
先来说一下局部的指令。
如下vue的官方文档的一个例子是。
new Vue({
el: '#app',
data: {
message: 'hello!'
},
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
})
使用的方式是
<div id="app">
<input type="text" name="" value="" v-focus >
</div>
我们定义了一个叫做focus的指令,它的作用就是在inserted的时候,让一个input获得焦点。
其中directives 是一个对象,它拥有多个方法(或者称之为钩子函数更为贴切)。
关于directives的方法,如下官方文档的说法:
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
意思是,这个钩子函数只被调用一次,如果你想在某些变量发生变化的情况下,重新的执行指令,那么该钩子函数不行。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
VNode 是虚拟的DOM节点,vue会在虚拟的DOM的基础上渲染为真实的DOM
也就是说这个函数是随着数据的更新变化而重新执行的。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
插入的时候调用一次
以上是directives 指令所接受的几个钩子函数,至于如何选择,那么根据你的需求。
如果是一次性的指令,直接使用inserted 或者是bind钩子, 所谓的一次性指令,就是,只在插入的时候执行一次,即使绑定的变量发生改变也不会重新触发指令。
官方提供了一个更为简单的写法:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
不需要写钩子函数,直接写操作的逻辑。 如上,构建了一个指令,名字叫做color-swatch.
而如果你希望你的指令能够根据数据的变化而重新执行的话,钩子函数就选择update。 效果如下:
<div id="hook-arguments-example" v-demo="message" >
{{message}}
</div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
var text = el.innerHTML
console.log('text',text)
el.innerHTML = text.split('').reverse().join('')
},
})
var vm = new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
如上边这个代码。那么我得到的结果是!olleh
但是,即使我使用vm.message="World"
那么结果不会发生任何变化.
但是如果改为
Vue.directive('demo', {
update:function(el, binding, vnode){
console.log('update触发了');
var s = JSON.stringify
var text = binding.value
console.log('text',text)
el.innerHTML = text.split('').reverse().join('')
}
})
使用update 钩子函数替代了bind钩子函数
那么每当所绑定的message发生变化的时候,指令就会触发。
但是,此时会有一个问题,如上所示的代码,你执行之后,发现,update并没有触发,也就是说没有打印出来“update触发了”。
有可能是因为,message一直有值,就不触发,那么如下。
Vue.directive('demo', {
update:function(el, binding, vnode){
console.log('update触发了');
var s = JSON.stringify
var text = binding.value
console.log('text',text)
el.innerHTML = text.split('').reverse().join('')
}
})
var vm = new Vue({
el: '#hook-arguments-example',
data: {
message: ''
},
created(){
this.message='Hello!'
}
})
我们在初始的时候message为空,而后,给它赋值。
虽然message已经被赋值了,且这个值没有发生变化,
就如上,vm实例中。message已经有值了,这个时候,那么我得到的结果是Hello。
指令依旧是没有触发。
但是,如果message的值是异步获取的话,那么指令一般会在数据加载之前被挂载的,
所以也就不会有初始化的时候,指令没有执行的问题
如下所示:
Vue.directive('demo', {
update:function(el, binding, vnode){
console.log('update触发了');
var s = JSON.stringify
var text = binding.value
console.log('text',text)
el.innerHTML = text.split('').reverse().join('')
}
})
var vm = new Vue({
el: '#hook-arguments-example',
data: {
message: ''
},
created(){
let vm = this
setTimeout(function(){
vm.message='Hello!'
},1000)
}
})
如上所示,通过异步执行的代码,就会在初始化的时候触发了一次指令。
但是,如果初始值设置了,而后你开始修改message的值vm.message="world"
那么指令也会触发,结果为dlow。
因为,指令认为数据发生了变化,所以会执行,但是,之前之所以不会执行的原因可能如下:
为什么,update会在初始的情况没有执行。
可能是因为,vue实例初始化,而后赋值这一系列的行为都是在指令绑定之前执行的。
vue 的created是率先执行的。其中created是发生在DOM渲染挂载之前的。
首先执行了created钩子函数,对message进行了赋值,而后,vue会生成虚拟的DOM
然后跟我上一次生成的Virtual DOM去 diff,得到一个Patch,然后把这个Patch打到浏览器的DOM上去。
所以说,created执行之后,message由空值,改变为'Hello!',
而用bind或者是inserted的话,这两个钩子函数不管数据是否更新,都会执行一遍。
而使用,update,则是指令在绑定之后,message已经被赋值了,message一直是“Hello”,根本没有发生改变,所以就不需要执行。
但是,使用异步的方式就不一样了,异步是最后执行的,在指令待机完成之后才会执行异步的一些操作,
DOM渲染完毕而后异步的方法开始执行,赋值了一次,重新生成虚拟DOM而后就触发了update,所以指令执行了。
这就是为什么,在同步的情况下绑定的数据初始化的时候,不会触发update。
另外,这一切都看着像是数据的修改触发了update指令的执行,但是,实际是
所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
实际就是只有在虚拟的节点发生改变之后,update才会被触发。
那么配合bind方法使用起来就更加舒爽。
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
var text = el.innerHTML
console.log('text',text)
el.innerHTML = text.split('').reverse().join('')
},
update:function(el, binding, vnode){
var s = JSON.stringify
var text = binding.value
console.log('text',text)
el.innerHTML = text.split('').reverse().join('')
}
})
如上,bind解决了初始化绑定的时候,没有执行指令的问题,update解决了绑定的值变动的问题。
钩子函数会接受一些参数、
钩子函数接收的参数,官方文档如下:
el:指令所绑定的元素,可以用来直接操作 DOM 。
这个是真实的DOM
binding:一个对象,包含以下属性:
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 编译生成的虚拟节点。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
以上的这些参数,除了,el可以被修改之外,其他的都是只读的。