第3章 Vue指令

指令(Directive)是特殊的带有前缀V-的特性。指令的值限定为绑定表达式,指令的指责就是当其表达式的值改变时把某些特殊的行为应用到DOM上。

3.1 内部指令

①v-if指令可以完全根据表达式的值在DOM中生成或移除一个元素。如果v-if表达式赋值为false,那么对应的元素就会从DOM中移除,否则,对应元素的一个克隆将被重新插入DOM中。

因为v-if是一个指令,需要将它添加到一个元素上。如果想切换多个元素,则可以把<template>元素当做包装元素,并在其上使用v-if,最终渲染结果不会包含它。

<template v-if="ok">
   <h1>Title</h1>
   <p>go for Spade<p>
</template>

②v-show指令是根据表达式的值来显示或隐藏HTML元素。当v-show赋值为false时,元素将被隐藏。查看DOM时,会发现元素上多了一个内联样式style="display:none"。 注:v-show不支持<template>语法

在切换v-if时,Vue.js有一个局部编译/卸载过程,因为v-i模板可能包括数据绑定或者子组件。v-if是真实的条件渲染,因为它会确保条件块在切换条件时合适的销毁与重建条件块内的事件监听器和子组件。

v-if是惰性的————如果初始渲染时条件为假,则什么也不会做,在条件第一次变为真时才开始局部编译(编译会被缓存起来)。

相比之下,v-show简单的多————元素始终被编译并保留,只是简单的基于css切换。

一般来说,v-if有更高的切换消耗,而v-show有更高的初始渲染消耗。因此,如果需要频繁的切换,则使用v-show较好,如果在运行时条件不大可能改变,则使用v-if较好。    

③v-else就是JavaScript中else的意思,它必须跟着v-if或v-show,充当else功能。
注意:将v-show用在组件上时,因为指令的优先级v-else会出现问题,所以不要将组件上的v-show后接v-else,可以用另一个v-show代替v-else。

④v-model指令用来在input、select、text、checekbox、radio等表单控件元素上创建双向数据绑定。根据控件类型v-model自动选取正确的方法更新元素。尽管有点神奇,但是v-model不过是语法糖,在用户输入事件中更新数据,以及特别处理一些极端例子。

v-model指令后面还可以添加多个参数(number、lazy、debounce)。
  (1)number
     如果想将用户的输入自动转换为Number类型(如果原值的转换结果为NaN,则返回原值),则可以添加一个number特性。
  (2)lazy
     在默认情况下,v-model在input事件中同步输入框的值与数据,我们可以添加一个lazy特性,从而将数据改到在change事件中发生。(没有改变,就不会将view的数据变化同步model)
  (3)debounce(防抖动函数)
     设置一个最小的延时,在每次敲击之后延时同步输入框的值与数据。如果每次更新都要进行高耗操作(例如:在input中输入内容时要随时发送AJAX请求),那么它较为有用。<input v-model="msg" debounce="5000"> 注:内容在5000ms后改变

⑤v-for我们可以使用v-for指令基于源数据重复渲染元素。我们也可以使用$index来呈现相对应的数组索引。
     <li v-for="item in items" class="item-{{$index}}">
        {{$index}} - {{item.msg}}
     </li>
v-for需要特殊的别名,形式为"item in items"(items 是数据数组,item是当前数组元素的别名)。v-for在开始时对传入的表达式做了语法分析,不是"item in/of items"的形式,将给出警告信息。注:Vue.js 1.0.17及以后版本支持of分隔符,更接近JavaScript遍历器语法。Vue.js 0.12.8及以后版本支持in分隔符。

使用v-for,将得到一个特殊的作用域,类似于AngularJS的隔离作用域,我们需要明确指定props属性传递数据,否则在组件内将获取不到数据。对于组件内的<p>标签,我们可以使用<slot>;

当数组数据出现变动时,Vue.js包装了被观察数组的变异方法,他们能触发视图更新。被包装的方法有:
push()  pop()  shift()  unshift()  splice()  sort()  reserve()
<!--源码目录:vue/src/observer/array.js 10 行-->
Vue.js重写这些方法之后。触发了一次notify。
Vue.js还增加了两个方法来观测变化:$set、$remove。
我们应该尽量避免直接设置数据绑定的数组元素,因为这些变化不会被Vue.js检测到,因而也不会更新视图渲染。这时,可以使用$set方法:
// same as demo.items[0]=&emsp;... but triggers view update
demo.items.$set(0,{childMsg:'Changed!'})

$remove是splice的语法糖,用于从目标数组中查找并删除元素。因此,不必:
  var index = this.items.indexOf(item)
  if(index !== -1){
    this.items.splice(index,1)
  }
只用这样:
  demo.items.$remove(item)
另外也可以使用filter、concat、slice方法,返回的数据将是一个不同的实例。我们可以用新的数组替换原来的数组。
  demo.items = demo.items.filter(function (item){
    return item.childMsg.match(/Hello)
  }) 

在某些情况下,我们有时可能需要全新对象(例如:通过API调用创建的对象)来替换数组。因为在默认情况下,v-for通过数据对象的特征来决定自己有作用域和DOM元素的复用程度,这可能导致重新渲染整个列表。但是,如果每个对象都有唯一的ID属性,便可以使用track-by特性给Vue.js一个提示,因而Vue.js尽可能的复用已有实例。
  假定数据为:
  { 
    itrms:[
      {_uid:'88f869d',...},
      {_uid:'7496c10',...} 
    ]
  }
  <div v-for = "item in items" track-by="_uid"></div>

在替换数组items时,如果Vue.js遇到一个包含有_uid:'88f869d'的新对象,那么它知道可以复用这个已有对象的zuoyongyu与DOM元素。

如果没有唯一键供追踪,则可以使用track-by="$index",它强制让v-for进入原位更新模式:片段不会被移动,而是简单地以对应索引的新值刷新。这种模式也能处理数组数据中重复的值。

这让数据替换非常高效,但是也会付出一定的代价。因为这时DOM节点不再映射数组元素顺序的改变,不能同步临时状态(比如<input>元素的值),以及组件的私有状态。因此,如果v-for块包含<input>元素或子组件,则要小心使用track-by="$index"。

因为JavaScript的限制,Vue.js不能检测到下面数组的变化:
(1)直接用索引设置元素,如vm.items[0] = {}
(2)修改数据的长度,如vm.items.length = 0 
为了解决前一个问题,Vue.js扩展了观察数组,我们可以使用上面的$set方法:
    // 与examplel.items[0]=&emsp;... 相同,但是能触发视图更新
       vm.items.$set(0,{childMsg:'Changed!'})
至于后一个问题,只需要一个空数组替换items即可。

有时我们可能想重复一个包含多个DOM元素的块,在这种情况下,则可以使用<template>标签来包装重复片段。这里的<template>标签只充当一个语义包装器。代码如下:
<ul>
<template v-for="list in lists">
<li>{{list.msg}}</li>
<li class="divider"></li>
</template>
</ul>

我们也可以使用v-for遍历每一个对象,每一个重复的实例都将有一个特殊的属性$key,或者给对象的键值提供一个别名。
注:ECMAScript5无法检测到新属性添加到一个对象上或者在对象中删除。要处理这种情况Vue.js增加了三种方法:$add(key,value)、$set(key,value)和$delect(key),这些方法可以用来删除或者添加属性,同时触发视图更新。

v-for也支持整数。<div v-for="n in 10">lalala!</div>

v-for同时还可以和Vue.js提供内置过滤器或排序数据一起使用。
(1)filterBy(0.12版本)  //语法:filterBy searchKey [in dataKey...] 
用法:
<input v-model="searchText">
<ul>
  <li v-for="user in users | filterBy searchKey in 'name' ">{{user.name}}</li>
</ul>
数据如下:
user:[
  {
   name:'快车'      
   tag:'1'
  },                
  {
   name:'出租车',
   tag:'2'
  },     
  {
   name:'顺丰车',
   tag:'3'
  },
  {
   name:'专车',
   tag:'4'
  } 
 ]在<ul>标那个签中展示了滴滴业务类型,在输入框中输入“快车”,<ul>中数据会根据所输入的“快车”,在users的name字段中过滤出我们需要的信息,并展示出来。
(2)orderBy(0.12版本)  //语法:orderBy sortKey[reserverKey]
用法:
<ul>
  <li v-for="user in users | orderBy field reverse ">{{user.name}}</li>
</ul>
<script>
  var demo = new Vue({
    el:'#example',
    data:{
       filed:'tag',
       reverse:false,
       user:[{}]
    }
  })
</script>
在<ul>标签中根据field变量代表的tag字段正序排列数据。

⑥v-text指令可以更新元素的textContent。在内部,{{Mustache}}插值也被编译为textNode的一个v-text指令。
<span v-text="msg"></span>  <!--等同于-->  <span>{{msg}}</span>

⑦v-html指令可以更新元素的innerHTML。内部按普通HTML插入————数据绑定被忽略。如果想复用模板片段,则应该使用partials。
在内部,{{{MUstache}}}插值也会被编译为锚点上的一个v-html指令。
注:不建议在网站上直接动态渲染任意HTML片段,很容易导致XSS攻击。
<div v-html="html"></div>   <!--等同于-->  <div>{{{html}}}</div>

⑧v-bind指令用于响应更新HTML特性,将一个或多个attribute,或者一个组件prop动态绑定到表达式。v-bind可以简写为:
<!--绑定attribute-->
<img v-bind:src="imageSrc">
<!--缩写-->
<img :src="imageSrc">
在绑定class或style时,支持其他类型的值,如数组或对象。
<div :class="{classA,{classB:isB,classC:isC}}"></div>
<script>
  var demo = new Vue({
    el:'#example',
    data:{
       classA:'A',
       isB:false,
       isC:true 
    }
  })
//效果:classA和classC生效
</script>

没有参数时,可以绑定到一个对象。注意,此时class和style绑定不支持数组和对象(对象key会转换为小写)。
<body>
  <div  v-bind="{ id:someProp,'OTHERAttr':otherProp }"></div> 
</body>
<script>
  var exampleVM2 = new Vue({
    el:"#example",
    data:{
      someProp:'idName',
      otherprop:'prop'
    }
  })   
</script>
效果:<div id="idName" otherattr="prop"></div>

在绑定prop时,prop必须在子组件中声明。可以用修饰符指定不同的绑定类型。修饰符为:
  .sync————双向绑定,只能用于prop绑定。
  .once————单次绑定,只能用于prop绑定。
  .camel————将绑定的特性名字转换回驼峰命名。只能用于普通HTML特性的绑定,通常用于绑定用驼峰命名的SVG特性,比如viewBOX。
  
  <!-- prop绑定,"prop"必须在my-component组件内声明 -->
  <my-component :prop="someThing"><my-component>
  <!-- 双向 prop 绑定 -->
  <my-component :prop.sync="someThing"><my-component>
  <!-- 单向 prop 绑定 -->
  <my-component :prop.once="someThing"><my-component>

⑨v-on指令用于绑定事件监听器。事件类型由参数指定:表达式可以是一个人方法的名字或一个内联语句,如果没有修饰符,也可以省略。

使用在普通元素上时,只能监听原生DOM事件。使用在自定义元素组件上时,也可以监听子组件触发的自定义事件。

在监听原生DOM事件时,如果只定义一个参数,DOM event 为事件的唯一参数。如果在内联语句处理器中访问原生DOM事件,则可以用特殊变量$event把它传入方法。

Vue.js 1.0.11及以后版本在监听自定义事件时,内联语句可以访问一个$arguments属性,它是一个数组,包含了传给子组件的$emit回调的参数。

<!-- 方法处理器 -->
<button v-on:click="doThis"></button>
<!-- 内联语句 -->
<button v-on:click="doThat('hello',$event)"></button>
<!-- 缩写 -->
<button @click="doThis"></button>

v-on后面不仅可以跟参数,还可以增加修饰符:
  .stop ———— 调用 event.stopPropagation()。
  .prevent ———— 调用 event.preventDefault()。
  .capture ———— 添加事件侦听器时使用 capture 模式。
  .self ———— 只当事件时从侦听器绑定的元素本身触发时才触发回调。
  .{keyCode | keyAlias} ———— 只在指定按键上触发回调。Vue.js提供的键值有: 
   [esc:27、tab:9、enter:13、space:32、'delete':[8,46]、up:38、left:37、right:39、down:40]
  
  <!--停止冒泡-->  
  <button @click.stop="doThis"></button>
  <!--阻止默认行为-->
  <button @click.prevent="doThis"></button>
  <!--阻止默认行为,没有表达式-->
  <from @submit.prevent></from>
  <!--串联修饰符-->
  <button @click.stop.prevent="doThis">stop</button>
  <!--键修饰符,键别名-->
  <input @keyup.enter="onEnter">
  <!--键修饰符,键代码-->
  <input @keyup.13="onEnter">

⑩v-ref,在父组件上注册一个子组件的索引,便于直接访问。不需要表达式,必须提供参数id。可以通过父组件的$refs对象访问子组件。

当v-ref和v-for一起使用时,注册的值将是一个数组,包含所有的子组件,对应于绑定数组。如果v-for使用在一个对象上,注册的值将是一个对象,包含所有的子组件,对应于绑定对象。

注:因为HTML不区分大小写,camelCase风格的名字比如v-ref:someRef将全部转换为小写。可以用v-ref:some-ref设置this.$refs.someRef

⑪v-el,为DOM元素注册一个索引,方便通过所属实实例的$els访问这个元素。可以用v-el:some-el设置this.$els.someEl。
  <span v-el:msg>hello</span>
  <span v-el:other-msg>world</span>
  
  通过this.$els获取相应的DOM元素:
  this.$els.msg.textContent // -> "hello"
  this.$els.otherMsg.textContent // -> "world"

⑫v-pre,编译时跳过当前元素和它的子元素。可以用来显示原始Mustache标签。跳过大量没有指令的节点会加快编译。

⑬v-cloak,这个指令保持在元素上直到关联实例结束编译。AngularJS也提供了相同的功能。当和css规则如[v-cloak]{display:none}一起使用时,这个指令可以隐藏未编译的Mustache标签直到实例准备完毕,防止在渲染页面时,用户可能先看到Mustache标签,再看到编译后的数据。
用法:
    [v-cloak]{
       display:none;
    }
    <div v-cloak>
       {{message}}
    </div>
   

3.2 自定义指令

使用过AngularJS的一定清楚它的指令是使用directive(name,factory_function)实现的,格式如下:
  angular.module('myapp',[],)
   .directive(myDeirective,function(){
      return{
        template:'',
        restrict:'',
        template:'',
        replace:''
        ...
      }
   })

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

Vue.js用Vue.directive(id,defintion)方法注册一个全局自定义指令,它接收两个参数:指令ID与定义对象。也可以用组件的directives选项注册一个局部自定义指令(此方法相当于AngularJD restrict 属性值为A)。

(1)钩子函数:
AngularJS提供了两个函数:compile和link,其中编译函数主要负责将作用域和DOM进行链接,链接函数用来创建可以操作DOM指令。注意,compile和link选项是互斥的,如果同时设置这两个选项,则会把compile返回的函数当做link函数,而忽略link选项本身。Vue.js也提供了几个钩子函数(都是可选的,相互之间没有制约关系)。

bind ———— 只调用一次,在指令第一次绑定到元素上时调用。
update ———— 在bind之后立即以初始值为参数第一次调用,之后每当绑定值变化时调用,参数为新值与旧值。
unbind ———— 只调用一次,在指令从元素上解绑时调用。

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

在注册之后,便可以在Vue.js模板中这样使用(记得添加前缀v-):
  <div v-my-directive="someValue"></div>
当只需要updata函数时,可以传入一个函数替代定义对象:
  Vue.directive('my-directive',function(value){
    //这个函数作用 update()
  })

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

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

(3)对象字面量
如果指令需要多个值,则可以传入一个JavaScript对象字面量。记住,指令可以使用任意合法的JavaScript表达式。
(注:字面量表示如何表达这个值,一般除去表达式,给变量赋值时,等号右边都可以认为是字面量。
字面量分为字符串字面量(string literal )、数组字面量(array literal)和对象字面量(object literal),另外还有函数字面量(function literal)。
示例:
  var test="hello world!";
  "hello world!"就是字符串字面量,test是变量名。)

(4)字面修饰符
当指令使用了字面修饰符literal时,他的值将按普通字符串处理并传递给update方法。update方法将只调用一次,因为普通字符换不能响应数据变化。
<div id='demo' v-demo.literal=""></div> 

(5)元素指令
有时我们想以自定义元素的形式使用指令,而不是以属性的形式。这与AngularJS的"E"指令非常相似。元素指令可以看作是一个轻量组件。
元素指令不能接受参数或表达式,但是它可以读取元素的特性,从而决定它的行为。
不同于普通指令及其子元素————只有该元素指令本身可以操作该元素及其子元素。

高级选项:自定义指令提供一种机制将数据的变化映射为DOM行为。

(1)params,自定义指令可以接受一个params数组,指定一个特性列表,Vue编译器将自动提取绑定元素的这些特征。
此API也支持动态属性。this.params[key]会自动保持更新。另外,可以指定一个回调,在值变化时调用。
注:类似于props,指令参数的名字在JavaScript中使用camelCase风格,在HTML中对应使用kebab-case风格。例如,假设在模板中有一个参数disable-effect,在JavaScript中以disableEffect访问它。 

(2)deep,如果自定义指令使用在一个对象上,当对象内部属性变化时要触发update,则在指令定义对象中指定deep:true。
Vue.directive('my-directive',{
  deep:true,
  update:function(obj){
     //当obj的嵌套属性变化时调用
     console.info(obj.b.c)
  }
})    

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

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

(5)Terminal(1.0.19及以后版本),Vue通过递归遍历DOM树来编译模块。但是当它遇到terminal指令时会停止遍历这个元素的后代元素,这个指令将接管编译这个元素及其后代元素的任务。v-if和v-for都是terminal指令。
编写自定义terminal指令是一个高级话题,需要较好滴理解Vue的编译流程,但并不是说不可能编写自定义terminal指令。用terminal:true指定自定义terminal指令,可能还需要用Vue.FragmentFactory来编译partial。下面是一个自定义terminal指令,它编译其内容模板并将结果注入到页面的另一个地方。(如果想编写自定义terminal指令,建议通读内置terminal指令的源码,如v-if和v-for,以便更好滴了解Vue.js内部机制。)

(6)priority,可以给指令指定一个优先级。如果没有指定优先级,普通指令默认是1000,terminal指令默认是2000。同一个元素上优先级高的指令会比其它指令处理得早一些,优先级一样的指令按照它在元素特性列表中出现的顺序依次处理,但是不能保证这个顺序在不同的浏览器中是一致的。
另外,流程控制指令v-if和v-for在编译过程中始终拥有最高优先级。

3.3内部解析
3.4常见问题解析

①v-on可以绑定多个方法么?
v-on可以绑定多种类型方法,可以是click事件,可以是focus事件,也可以是change事件,根据业务需求进行选择。但是,如果v-on绑定了两个或者多个click事件,那么v-on只会绑定第一个click事件,其它的会被忽略。
<input type="text" :value="name" @input="OnInput" @focus="onFocus" @blur="OnBlur">          

②一个Vue实例可以绑定多个element元素么?
这个疑问的产生是没有理解el的概念。el为实例提供挂在元素,值可以是CSS选择符,或实际的HTML元素,或返回HTML元素的函数。注意,元素只用作挂载点。如果提供了模板,则元素被替换,除非replace为false。元素可以用vm.$el访问。
用在Vue.extend中必须是函数值,这样所有实例不会共享元素。
如果在初始化时指定了这个选项,实例将立即进入编译过程,否则需要调用vm.$mount(),手动开始编译。

③在Vue中如何让v-for循环出来的列表里面的click事件只对当前列表内元素有效?
从数据角度出发,定义好数据结构,然后操作数据。
或者通过$event对象,获取当前事件源,然后操作下面的span元素,方法很多。

tips:
toggle:在javascript中toggle()为连续点击事件,当里面含有多个function(){}函数时,每次点击依次执行里面的function的函数,直至最后一个.随后每次点击都重复对这几个函数的轮番调用,toggle的语法如下:

toggle(fn1,fn2,fn3·····fnN);
但当toggle(),不带参数时,与show()和hide()的作用一样,切换元素的可见状态,如果元素是可见的,则切换为隐藏状态;如果元素是隐藏的则切换为可见状态,此时括号内可添加()毫秒(如1000)等),slow,normal,fast等;

toggleClass( )与toggle( )差不多,如果里面含有class样式则移除,没有的话则添加;

原文地址:https://www.cnblogs.com/goforxiaobo/p/12465936.html