Vue.js 前端开发 快速入门与专业应用
上QQ阅读APP看书,第一时间看更新

3.3 指令的高级选项

Vue.js指令定义对象中除了钩子函数外,还有一些其他的选项,我们将在本节中对其做逐个的讲述。

3.3.1 params

定义对象中可以接受一个params数组,Vue.js编译器将自动提取自定义指令绑定元素上的这些属性。例如:

        <div v-my-advance-directive a="paramA"></div>
        Vue.directive('my-advance-directive', {
          params : ['a'],
          bind : function() {
            console.log('params', this.params);
          }
        });

除了直接传入数值外,params支持绑定动态数据,并且可以设定一个watcher监听,当数据变化时,会调用这个回调函数。例如:

        <div v-my-advance-directive v-bind:a="a"></div>
        // 当然也可以简写成  <div v-my-advance-directive :a="a"></div>
        Vue.directive('my-advance-directive', {
          params : ['a'],
          paramWatchers : {
            a : function(val, oldVal) {
                console.log('watcher: ', val, oldVal)
            }
          },
          bind : function() {
            console.log('params', this.params);
          }
        });
        var vm = new Vue({
          el : '#app',
           data : {
            a : 'dynamitc data'
           }
        });

3.3.2 deep

当自定义指令作用于一个对象上时,我们可以使用deep选项来监听对象内部发生的变化。例如:

        <div v-my-deep-directive="obj"></div>
        <div v-my-nodeep-directive="obj"></div>
        Vue.directive('my-deep-directive', {
          deep : true,
          update : function(newValue, oldValue) {
            console.log('deep', newValue.a.b);
          }
        });
        Vue.directive('my-nodeep-directive', {
          update : function(newValue, oldValue) {
            console.log('deep', newValue.a.b);
          }
        });
        var vm = new Vue({
          el : '#app',
          data : {
            obj : {
              a : {
                  b : 'inner'
              }
            }
          }
        })

运行后,在控制台中输入vm.obj.a.b = 'inner changed',只有my-deep-directive调用了update函数,输出了改变后的值。

Vue.js 2.0中废弃了该选项。

3.3.3 twoWay

在自定义指令中,如果需要向Vue实例写回数据,就需要在定义对象中使用twoWay:true,这样可以在指令中使用this.set(value)来写回数据。

        <input type="text" v-my-twoway-directive="param" / >
        Vue.directive('my-twoway-directive', {
          twoWay : true,
          bind : function() {
            this.handler = function () {
                console.log('value changed: ', this.el.value);
                this.set(this.el.value)
            }.bind(this)
            this.el.addEventListener('input', this.handler)
          },
          unbind: function () {
            this.el.removeEventListener('input', this.handler)
          }
        });
        var vm = new Vue({
          el : '#app',
          data : {
            param : 'first',
          }
        });

此时在input中输入文字,然后在控制台中输入vm.param即可观察到实例的param属性已被改变。

需要注意的是,如果没有设定twoWay:true,就在自定义指令中调用this.set(), Vue.js会抛出异常。

3.3.4 acceptStatement

选项acceptStatement:true可以允许自定义指令接受内联语句,同时update函数接收的值是一个函数,在调用该函数时,它将在所属实例作用域内运行。

        <div v-my-directive="i++"></div>
        Vue.directive('my-directive', {
          acceptStatement: true,
          update: function (fn) {
          }
        })
        var vm = new Vue({
          el : '#app',
          data : {
            i : 0
          }
        });

如果在update函数中,运行fn(),则会执行内联语句i++,此时vm.i = 1。但更改vm.i并不会触发update函数。

需要当心的是,如果此时没有设定acceptStatement: true,该指令会陷入一个死循环中。v-my-statement-directive接受到i的值每次都在变化,会重复调用update函数,最终导致Vue.js抛出异常。

3.3.5 terminal

选项terminal的作用是阻止Vue.js遍历这个元素及其内部元素,并由该指令本身去编译绑定元素及其内部元素。内置的指令v-if和v-for都是terminal指令。

使用terminal选项是一个相对较为复杂的过程,你需要对Vue.js的编译过程有一定的了解,这里借助官网的一个例子来大致说明如何使用terminal。

        <div id="modal"></div>
        ...
        <div v-inject:modal>
          <h1>header</h1>
          <p>body</p>
          <p>footer</p>
        </div>
        var FragmentFactory = Vue.FragmentFactory // Vue.js全局API,用来创造fragment
    的工厂函数,fragment中包含了具体的scope和DOM元素,可以看成一个独立的组件或者实例。
        var remove = Vue.util.remove // Vue.js工具类函数,移除DOM元素
        var createAnchor = Vue.util.createAnchor  // 创建锚点,锚点在debug模式下是
    注释节点,非debug模式下是文本节点,主要作用是标记dom元素的插入和移除
        Vue.directive('inject', {
          terminal: true,
          bind: function () {
            var container = document.getElementById(this.arg) // 获取需要注入到的DOM元素
            this.anchor = createAnchor('v-inject')  // 创建v-inject锚点
            container.appendChild(this.anchor) // 锚点挂载到注入节点中
            remove(this.el) // 移除指令绑定的元素
            var factory = new FragmentFactory(this.vm, this.el)  // 创建fragment
            this.frag = factory.create(this._host, this._scope, this._frag)
            // this._host用于表示存在内容分发时的父组件
            // this._scope用于表示存在v-for时的作用域
            // this._frag用于表示该指令的父fragment
            this.frag.before(this.anchor)
          },
          unbind: function () {
            this.frag.remove()
            remove(this.anchor)
          }
        })

最终我们得到的结果是:

3.3.6 priority

选项priority即为指定指令的优先级。普通指令默认是1000, termial指令默认为2000。同一元素上优先级高的指令会比其他指令处理得早一些,相同优先级则按出现顺序依次处理。以下为内置指令优先级顺序:

        export const ON = 700
        export const MODEL = 800
        export const BIND = 850
        export const TRANSITION = 1100
        export const EL = 1500
        export const COMPONENT = 1500
        export const PARTIAL = 1750
        export const IF = 2100
        export const FOR = 2200
        export const SLOT = 2300