指令

187 阅读7分钟

在我看来,指令的作用就是为了能够直接的来修改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可以被修改之外,其他的都是只读的。