Vue.js每天必学之指令系统与自定义指令


Posted in Javascript onSeptember 07, 2016

基础

除了内置指令,Vue.js 也允许注册自定义指令。自定义指令提供一种机制将数据的变化映射为 DOM 行为。

可以用 Vue.directive(id, definition) 方法注册一个全局自定义指令,它接收两个参数指令 ID 与定义对象。也可以用组件的 directives 选项注册一个局部自定义指令。

钩子函数

定义对象可以提供几个钩子函数(都是可选的):

 •bind:只调用一次,在指令第一次绑定到元素上时调用。

 •update: 在 bind 之后立即以初始值为参数第一次调用,之后每当绑定值变化时调用,参数为新值与旧值。

 •unbind:只调用一次,在指令从元素上解绑时调用。

 示例

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

在注册之后,便可以在 Vue.js 模板中这样用(记着添加前缀 v-):

<div v-my-directive="someValue"></div>

当只需要 update 函数时,可以传入一个函数替代定义对象:

Vue.directive('my-directive', function (value) {
 // 这个函数用作 update()
})

指令实例属性

所有的钩子函数将被复制到实际的指令对象中,钩子内 this 指向这个指令对象。这个对象暴露了一些有用的属性:
 •el: 指令绑定的元素。
 •vm: 拥有该指令的上下文 ViewModel。
 •expression: 指令的表达式,不包括参数和过滤器。
 •arg: 指令的参数。
 •name: 指令的名字,不包含前缀。
 •modifiers: 一个对象,包含指令的修饰符。
 •descriptor: 一个对象,包含指令的解析结果。 

你应当将这些属性视为只读的,不要修改它们。你也可以给指令对象添加自定义属性,但是注意不要覆盖已有的内部属性。

示例:

<div id="demo" v-demo:hello.a.b="msg"></div>

Vue.directive('demo', {
 bind: function () {
  console.log('demo bound!')
 },
 update: function (value) {
  this.el.innerHTML =
   'name - '    + this.name + '<br>' +
   'expression - ' + this.expression + '<br>' +
   'argument - '  + this.arg + '<br>' +
   'modifiers - ' + JSON.stringify(this.modifiers) + '<br>' +
   'value - '   + value
 }
})
var demo = new Vue({
 el: '#demo',
 data: {
  msg: 'hello!'
 }
})

对象字面量

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令可以使用任意合法的 JavaScript 表达式:

<div v-demo="{ color: 'white', text: 'hello!' }"></div>

Vue.directive('demo', function (value) {
 console.log(value.color) // "white"
 console.log(value.text) // "hello!"
})

字面修饰符

当指令使用了字面修饰符,它的值将按普通字符串处理并传递给 update 方法。update 方法将只调用一次,因为普通字符串不能响应数据变化。

<div v-demo.literal="foo bar baz">

Vue.directive('demo', function (value) {
 console.log(value) // "foo bar baz"
})

元素指令

有时我们想以自定义元素的形式使用指令,而不是以特性的形式。这与 Angular 的 “E” 指令非常相似。元素指令可以看做是一个轻量组件。可以像下面这样注册一个自定义元素指令:

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

不这样写:

<div v-my-directive></div>

这样写:

<my-directive></my-directive>

元素指令不能接受参数或表达式,但是它可以读取元素的特性从而决定它的行为。

迥异于普通指令,元素指令是终结性的,这意味着,一旦 Vue 遇到一个元素指令,它将跳过该元素及其子元素——只有该元素指令本身可以操作该元素及其子元素。

高级选项

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 在编译过程中始终拥有最高的优先级。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js中的前绑定和后绑定详解
Aug 01 Javascript
Jquery 类网页微信二维码图块滚动效果具体实现
Oct 14 Javascript
js数组如何添加json数据及js数组与json的区别
Oct 27 Javascript
超漂亮的jQuery图片轮播特效
Nov 24 Javascript
基于JavaScript实现手机短信按钮倒计时(超简单)
Dec 30 Javascript
Bootstrap Table使用方法详解
Aug 01 Javascript
轻松实现js选项卡切换效果
Sep 24 Javascript
Vue 父子组件、组件间通信
Mar 08 Javascript
thinkjs 文件上传功能实例代码
Nov 08 Javascript
swiper 自动图片无限轮播实现代码
May 21 Javascript
小程序文字跑马灯效果
Dec 28 Javascript
浅谈JavaScript浅拷贝和深拷贝
Nov 07 Javascript
Vue.js每天必学之过滤器与自定义过滤器
Sep 07 #Javascript
详解AngularJS中ng-src指令的使用
Sep 07 #Javascript
AngularJS实现按钮提示与点击变色效果
Sep 07 #Javascript
JS实现简单易用的手机端浮动窗口显示效果
Sep 07 #Javascript
利用Angularjs实现幻灯片效果
Sep 07 #Javascript
轻松掌握JavaScript状态模式
Sep 07 #Javascript
JS简单实现tab切换效果的多窗口显示功能
Sep 07 #Javascript
You might like
PHP程序开发范例学习之表单 获取文本框的值
2011/08/08 PHP
PHP数据库调用类调用实例(详细注释)
2012/07/12 PHP
PHP PDOStatement对象bindpram()、bindvalue()和bindcolumn之间的区别
2014/11/20 PHP
php的mail函数发送UTF-8编码中文邮件时标题乱码的解决办法
2015/10/20 PHP
js下通过prototype扩展实现indexOf的代码
2010/12/08 Javascript
js如何判断不同系统的浏览器类型
2013/10/28 Javascript
JavaScript关闭当前页面(窗口)不带任何提示
2014/03/26 Javascript
javascript初学者常用技巧
2014/09/02 Javascript
javascript学习笔记(一)基础知识
2014/09/30 Javascript
一个JavaScript操作元素定位元素的实例
2014/10/29 Javascript
基于jQuery实现咖啡订单管理简单应用
2017/02/10 Javascript
react-redux中connect()方法详细解析
2017/05/27 Javascript
小程序实现发表评论功能
2018/07/06 Javascript
微信小程序日历效果
2018/12/29 Javascript
说说Vue.js中的functional函数化组件的使用
2019/02/12 Javascript
vue之延时刷新实例
2019/11/14 Javascript
如何在 Vue 中使用 JSX
2021/02/14 Vue.js
Python列表(list)、字典(dict)、字符串(string)基本操作小结
2014/11/28 Python
详解Python中 __get__和__getattr__和__getattribute__的区别
2016/06/16 Python
Python中内置的日志模块logging用法详解
2016/07/12 Python
django 按时间范围查询数据库实例代码
2018/02/11 Python
浅谈python脚本设置运行参数的方法
2018/12/03 Python
pandas分别写入excel的不同sheet方法
2018/12/11 Python
Pycharm 2019 破解激活方法图文详解
2019/10/11 Python
英国在线珠宝店:The Jewel Hut
2017/03/20 全球购物
澳大利亚100%丝绸多彩度假装商店:TheSwankStore
2019/09/04 全球购物
马歇尔耳机官网:Marshall Headphones
2020/02/04 全球购物
销售找工作求职信
2013/12/20 职场文书
甜品店的创业计划书范文
2014/01/02 职场文书
大学生毕业鉴定
2014/01/31 职场文书
学习党的群众路线教育实践活动心得体会
2014/03/01 职场文书
正风肃纪剖析材料范文
2014/10/10 职场文书
党的群众路线教育实践活动个人对照检查材料(校长)
2014/11/05 职场文书
2014年度个人工作总结
2014/11/07 职场文书
如何用JavaScipt测网速
2021/05/09 Javascript
k8s部署redis cluster集群的实现
2021/06/24 Redis