Vuejs directive 指令进阶,意义与实战需要

建议先阅读官方教程

依然记得当初看 angularjs 时被 directive 触动的心情,可是毕竟我最后选择了用 vue.js 实施项目,所以这里要展开来说的是 vue.js 的 directive (指令)。

介绍

如果你看了 vue.js 的官网教程,你应该知道 v-on, v-bind, v-model, v-if, v-for, v-show, 等等,这些以 v- 开头作为属性放在 DOM 中的,都是指令,而且路由插件 vue-router,都是基于 directive 对页面切换进行控制的。可以说,directive 是框架的核心,所以,请消化它。

指令的职责就是当其表达式的值改变时把某些特殊的行为应用到 DOM 上,指令支持参数、修饰符,大大地强化了它的应用范围,使本身繁琐监听任务变得简洁有条理。

自定义一个指令

原始的指令定义方式如下

Vue.directive('my-directive', {
  bind: function () {
    // 准备工作
    // 例如,添加事件处理器或只需要运行一次的高耗任务
  },
  update: function (newValue, oldValue) {
    // 值更新时的工作
    // 也会以初始值为参数调用一次
  },
  unbind: function () {
    // 清理工作
    // 例如,删除 bind() 添加的事件监听器
  }
})

单文件模式中,指令可以定义在 directives 中

<script>
export default {
  directives: {
    'my-directive': {
      bind: function () {
        // 准备工作
        // 例如,添加事件处理器或只需要运行一次的高耗任务
      },
      update: function (newValue, oldValue) {
        // 值更新时的工作
        // 也会以初始值为参数调用一次
      },
      unbind: function () {
        // 清理工作
        // 例如,删除 bind() 添加的事件监听器
      }
    }
  }
}
</script>

用v-if来改变指令对象的显隐会触发bind和unbind,而v-show则不会,bind仅在组件ready时触发一次

指令的实例属性,参数、修饰符

所有的钩子函数将被复制到实际的指令对象中,钩子内 this 指向这个指令对象。这个对象暴露了一些有用的属性:

  • el: 指令绑定的元素。
  • vm: 拥有该指令的上下文 ViewModel。
  • expression: 指令的表达式,不包括参数和过滤器。
  • arg: 指令的参数。
  • name: 指令的名字,不包含前缀。
  • modifiers: 一个对象,包含指令的修饰符。
  • descriptor: 一个对象,包含指令的解析结果。

简易 DEMO

以下例子中通过指令 demo 把对象拆解并以表格形式输出到 dom 中

<template>
  <div v-print:table="article"></div>
</template>
<script>
export default {
  ready () {},
  data () {
    return {
      article: {
        title: '测试的标题',
        author: 'COoL',
        content: '测试的内容',
        createdate: '2016-09-11'
      }
    }
  },
  directives: {
    print: {
      bind: function () {},
      update: function (value) {
        if (this.arg === 'table') {
          let htmlStr = '<table>'
          for (let key in value) {
            htmlStr += '<tr><th>' + key + ':</th><td>' + value[key] + '</td></tr>'
          }
          htmlStr += '</table>'
          this.el.innerHTML = htmlStr
        }
      },
      unbind: function () {}
    }
  }
}
</script>
<style>
table{
  border: 0;
}
th,td{
  margin: 0;
  padding: 10px;
  border: 1px solid #dddddd;
  border-collapse:collapse;
  text-align: left;
  color: #666666;
}
</style>

此 DEMO 中,指令 print 的作用在于遍历一个对象并打印其中的元素,参数 table 定义的是打印的方式

高级选项

这里没有什么废话,直接搬运官方教程

  • params

    自定义指令可以接收一个 params 数组,指定一个特性列表,Vue 编译器将自动提取绑定元素的这些特性。例如:

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

    此 API 也支持动态属性。this.params[key] 会自动保持更新。另外,可以指定一个回调,在值变化时调用:

      <div v-example v-bind:a="someValue"></div>
      Vue.directive('example', {
        params: ['a'],
        paramWatchers: {
          a: function (val, oldVal) {
            console.log('a changed!')
          }
        }
      })
    

    类似于 props,指令参数的名字在 JavaScript 中使用 camelCase 风格,在 HTML 中对应使用 kebab-case 风格。例如,假设在模板里有一个参数 disable-effect,在 JavaScript 里以 disableEffect 访问它。

  • deep

    如果自定义指令用在一个对象上,当对象内部属性变化时要触发 update,则在指令定义对象中指定 deep: true。

      <div v-my-directive="obj"></div>
      Vue.directive('my-directive', {
        deep: true,
        update: function (obj) {
          // 在 `obj` 的嵌套属性变化时调用
        }
      })
    
  • twoWay

    如果指令想向 Vue 实例写回数据,则在指令定义对象中指定 twoWay: true 。该选项允许在指令中使用 this.set(value):

      Vue.directive('example', {
        twoWay: true,
        bind: function () {
          this.handler = function () {
            // 将数据写回 vm
            // 如果指令这样绑定 v-example="a.b.c"
            // 它将用给定值设置 `vm.a.b.c`
            this.set(this.el.value)
          }.bind(this)
          this.el.addEventListener('input', this.handler)
        },
        unbind: function () {
          this.el.removeEventListener('input', this.handler)
        }
      })
    
  • acceptStatement

    传入 acceptStatement:true 可以让自定义指令接受内联语句,就像 v-on 那样:

      <div v-my-directive="a++"></div>
      Vue.directive('my-directive', {
        acceptStatement: true,
        update: function (fn) {
          // 传入值是一个函数
          // 在调用它时将在所属实例作用域内计算 "a++" 语句
        }
      })
    

    明智地使用,因为通常你要在模板中避免副效应。

  • terminal

    1.0.19+

    Vue 通过递归遍历 DOM 树来编译模块。但是当它遇到 terminal 指令时会停止遍历这个元素的后代元素。这个指令将接管编译这个元素及其后代元素的任务。v-if 和 v-for 都是 terminal 指令。

    编写自定义 terminal 指令是一个高级话题,需要较好的理解 Vue 的编译流程,但这不是说不可能编写自定义 terminal 指令。用 terminal: true 指定自定义 terminal 指令,可能还需要用 Vue.FragmentFactory 来编译 partial。下面是一个自定义 terminal 指令,它编译它的内容模板并将结果注入到页面的另一个地方:

      var FragmentFactory = Vue.FragmentFactory
      var remove = Vue.util.remove
      var createAnchor = Vue.util.createAnchor
      Vue.directive('inject', {
        terminal: true,
        bind: function () {
          var container = document.getElementById(this.arg)
          this.anchor = createAnchor('v-inject')
          container.appendChild(this.anchor)
          remove(this.el)
          var factory = new FragmentFactory(this.vm, this.el)
          this.frag = factory.create(this._host, this._scope, this._frag)
          this.frag.before(this.anchor)
        },
        unbind: function () {
          this.frag.remove()
          remove(this.anchor)
        }
      })
      <div id="modal"></div>
      ...
      <div v-inject:modal>
        <h1>header</h1>
        <p>body</p>
        <p>footer</p>
      </div>
    

    如果你想编写自定义 terminal 指令,建议你通读内置 terminal 指令的源码,如 v-if 和v-for,以便更好地了解 Vue 的内部机制。

  • priority

    可以给指令指定一个优先级。如果没有指定,普通指令默认是 1000, terminal 指令默认是 2000。同一个元素上优先级高的指令会比其它指令处理得早一些。优先级一样的指令按照它在元素特性列表中出现的顺序依次处理,但是不能保证这个顺序在不同的浏览器中是一致的。

    可以在 API 中查看内置指令的优先级。另外,流程控制指令 v-if 和 v-for 在编译过程中始终拥有最高的优先级。

实战意义

在我的移动应用中,v-tap的使用意义重大

需求:vue提供的v-on不能监听手指的点击,v-tap指令会创建一个 dom 监听,通过手指的touchstart,touchmovetouchend动作来判断用户手指的动作是否为点击,是点击则执行用户的响应方法

插件传送门:https://github.com/MeCKodo/vue-tap

源码就不贴了,请自行查看文件 vue-tap.js

元素指令(已在2.0中被移除)

简单点说:引用官方教程一句话,元素指令可以看做是一个轻量组件。它相比指令,缺少表达式、参数、修饰符,而且它是终结性的,里面的代码不会进行自动绑定,而仅仅是字符串。换个角度,在bind的时候终结元素恰恰是节省资源的利器。

  • 定义方式

      Vue.elementDirective('my-directive', {
        // API 同普通指令
        bind: function () {
          // 操作 this.el...
        }
      })
    

    单文件模式中,指令定义在中

      <script>
      export default {
        elementDirectives: {
          'my-directive': {
            bind: function () {
              // 操作 this.el...
            }
          }
        }
      }
      </script>
    
  • 实战

    作为终结元素显示文章对象

      <template>
        <ul>
          <articleitem v-for="article in articles" :o="article"></articleitem>
        </ul>
      </template>
      <script>
      export default {
        data () {
          return {
            articles: [
              {
                title: '测试标题一',
                createdate: '2016-09-12'
              },
              {
                title: '测试标题二',
                createdate: '2016-09-11'
              },
              {
                title: '测试标题三',
                createdate: '2016-09-10'
              },
              {
                title: '测试标题四',
                createdate: '2016-09-09'
              },
              {
                title: '测试标题五',
                createdate: '2016-09-08'
              }
            ]
          }
        },
        elementDirectives: {
          articleitem: {
            params: ['o'],
            bind: function () {
              this.el.innerHTML = `
              <li>
                <a href="">${this.params.o.title}</a>
                <span>${this.params.o.createdate}</span>
              </li>
              `
            }
          }
        }
      }
      </script>
      <style>
      </style>
    
若您觉得我的博文对您有帮助,欢迎点击下方按钮对我打赏
打赏