一、什么是指令
上一篇我们讲到组件,组件是为了实现代码复用,将业务逻辑,UI模板封装成一个自定义的"标签"供外部使用,但有些时候我们用普通的dom也能实现相关的功能,但需要对功能进行一些"改造",使我们的实现更简洁,此时就会用到我们的指令。
二、内置指令
vue提供大量的内置的指令,先来总览下这些指令:
v-bind:动态的绑定一个或者多个特性值。
v-on:绑定监听事件,用户监听事件的变化。
v-model:表单组件上的双向绑定,如input
v-show:条件渲染,确定元素的显示或者隐藏
v-html:富文本渲染,作用类似于innerHTML
v-for:循环指令,对修饰的元素或者模板进行循环
v-if,v-else,v-else-if,条件指令,这几条指令配合使用,对元素进行条件渲染。
v-pre,在模板中跳过编译过程,直接输出原始值,如<div v-pre>{{msg}}</div>,输出的是"{{msg}}"
v-cloak:这个指令保持在元素上直到关联实例结束编译;这个说法有点晦涩,<div >{{msg}}</div>,有时会先显示"{{msg}}",然后再显示实际绑定值,导致闪屏,利用v-cloak配合display:none使用,待元素准备完毕后再显示,可解决此问题。
v-once:仅渲染一次,后续的元素将不再渲染。
下面我们对常用的指令的一些使用方法进行说明
三、v-bind
v-bind作为绑定命令,可以使用在属性,样式,以及组件的数据传递。
1、属性
以img的src为例,我们想动态的改变img的地址,那么使用绑定命令,绑定data中的src
<!--属性绑定-->
<img v-bind:src="src">
...
data:function() {
return {
src:require('./assets/logo.png'),
}
}
2、样式
有时我们要动态的改变class样式,vue无法直接对css操作,但是通过属性变量可以动态的切换不同的class。如在激活状态下我们要加载名为actionInfo的class,可以有以下几种表达方式。
class直接绑定data的属性,通过watch监听状态并改变属性值,从而改变样式
<!--绑定属性-->
<div v-bind:class="activeInfo">333</div>
...
data:function() {
return {
isActive:true,
activeInfo:'activeInfo'
}
},
watch:{
isActive:function(val){
val?this.activeInfo='activeInfo':this.activeInfo=''
}
}
...
.activeInfo{
border:1px red solid;
}
我们可以再简单些,在模板中直接采用三元计算(数组模式)
<!--二元计算-->
<div v-bind:class="isActive?'activeInfo':''">111</div>
再简单些有没有(对象模式)
<div v-bind:class="{activeInfo:isActive}">2222</div>
还可以对于多条件的class进行操作
<!--数组模式-->
<div v-bind:class="[isActive?'activeInfo':'',info]">111</div>
<!--对象模式-->
<div v-bind:class="{activeInfo:isActive,info}">2222</div>
...
data:function() {
return {
isActive:true,
activeInfo:'activeInfo',
info:'info'
}
}
}
...
.activeInfo{
border:1px red solid;
}
.info{
color: green
}
除了可以动态改变class样式,也可以动态改变内联的style样式,使用方式和上述一致,只是绑定的属性值是具体的样式,而不是class名而已
<!---对象模式-->
<div v-bind:style="{color:color,fontSize:fontSize+'px'}">444</div>
...
data:function() {
return {
color:'green',
fontSize:40
}
}
这里要注意下,color,fontSize是js的css对象名,对应的css属性名是color,font-size,也可以用css属性名(需要用单引号)
<!---css的属性名-->
<div v-bind:style="{'color':color,'font-size':fontSize+'px'}">444</div>
采用数组模式,需要注意的是,数组元素需要是对象。
<!--数组模式-->
<div v-bind:style="[color,fontSize]">5555</div>
....
data:function() {
return {
color:{
color:'green'
},
fontSize:{
fontSize:"40px"
}
}
},
3、prop数组传递
父组件给子组件数组传递时,通过绑定父组件的对象,映射到子组件的prop上,这个在组件中有详细介绍,大家可以查阅。
v-bind输入闲麻烦,有缩写的方法
<div v-bind:style="[color,fontSize]">5555</div>
等价于:
<div :style="[color,fontSize]">5555</div>
其实v-bind的作用就是声明式渲染,类似于{{activeInfo}},个人认为v-bind的设计有些画蛇添足,何不和声明式渲染合一呢,采用如下的方式就能搞定呢,当然这只是我的一些愚见。
<!--绑定属性-->
<div class="{{activeInfo}}">333</div>
四、v-on
v-on指令监听DOM事件,主要分为两类,一类是对原生事件的监听,如button的click,form的submit等,一类是对自定义事件的监听,这类主要是子组件采用emit发送事件,父组件通过v-on绑定事件进行监听。
首先我们来看下原生事件的监听的例子
<!--click-->
<button v-on:click="changeCls">555</button>
...
methods:{
changeCls:function(e){
console.log(e);
}
}
v-on:click="changeCls"表示监听button的点击事件,处理方法为changeCls,在methods中定义了该方法体,默认传入event事件对象。
也支持内联处理器中的调用方法,可以带上自定义的参数
<!--内联方法-->
<button v-on:click="changeCls(name)">555</button>
export default {
name: 'App',
data:function() {
return {
name:'click me',
}
},
methods:{
changeCls:function(name){
console.log(name);
}
}
}
有时方法中也需要使用原生的事件对象event,比如处理冒泡事件,可以通过$event传入:
<!--传入event,阻止冒泡-->
<div v-on:click="changeCls('div',$event)">
<button v-on:click="changeCls('button',$event)">555</button>
</div>
methods:{
changeCls:function(name,event){
//阻止冒泡
event.stopPropagation();
console.log(name);
}
}
为了方便处理冒泡,事件捕获等事件,vue提供了事件修饰符,上面的冒泡可以改写为:
<!--传入event,阻止冒泡-->
<div v-on:click="changeCls('div',$event)">
<button v-on:click.stop="changeCls('button')">555</button>
</div>
methods:{
changeCls:function(name){
//阻止冒泡
console.log(name);
}
}
通过加上.stop修饰符,使得代码更加简洁。相关的修饰符如下:
.prevent,同e.preventDefault(),阻止默认行为。
.capture,事件捕获模式。
.self,只有event.target对象是节点本身时才会触发。
.once,仅触发一次
其他还有按键,鼠标等相关事件修饰符,可以查阅相关的用法。
另外对于自定义事件的监听,在组件中有详细的介绍。
五、v-if,v-else,v-else-if
这几个指令组合一起使用,可以实现条件渲染。在实际项目中,我们经常会遇到根据不同的条件,而实现不同的页面呈现。比如,我们会根据请求返回的结果(成功,失败,其他未知)而展示不同的提示结果。
<!--条件渲染-->
<div class="info">
<!--成功-->
<template v-if="result=='success'">
<img src="./assets/success.png"/>
<span>操作成功</span>
</template>
<!--失败-->
<template v-else-if="result=='error'">
<img src="./assets/error.png"/>
<span>操作失败</span>
</template>
<!--其他未知-->
<template v-else>
<img src="./assets/unkown.png"/>
<span>其他未知</span>
</template>
</div>
当result=success时,看下实际的渲染dom结果:
仅将符合条件的代码模块渲染出来,其他的将不会加入到dom树,通过改变result的值,就可以渲染不同的代码块。
v-show指令也能控制代码块的显示和隐藏。它与v-if有如下的区别。
1、v-show总是将代码块渲染出来,然后控制其隐藏还是显示,所以代码块会在dom树中。
2、v-if仅将符合条件的渲染出来,加入dom树,其他的不会渲染。
所以两者区别与display,visibility原理一样。那实际项目中,应该用哪个呢,一般遵循以下原则:
如果频繁的切换,就用v-show,减少dom操作的开销。反之则用v-if,可以减少dom的节点数。
六、v-for
v-for指令实现列表渲染。列表是实际项目中最常用的组件,使用v-for绑定数组对象,很方便的实现列表渲染。
<!--tab标签-->
<ul>
<li v-for="tab in tabs">{{tab.name}}</li>
</ul>
data:function() {
return {
tabs:[
{name:'tab1'},
{name:'tab2'},
{name:'tab3'}
]
}
},
v-for语法与javascript的一致,很好理解。
有些情况下,我们要为每个元素增加唯一表示key,vue为我们提供可:key指令,配合v-for使用
<!--tab标签-->
<ul>
<li v-for="tab in tabs" :key="tab.id">{{tab.name}}</li>
</ul>
data:function() {
return {
tabs:[
{name:'tab1',id:'1'},
{name:'tab2',id:'2'},
{name:'tab3',id:'3'}
]
}
},
如果数据没有提供id作为我们的唯一标识,我们也可以使用索引值
<!--tab标签-->
<ul>
<li v-for="(tab,index) in tabs" :key="index">{{tab.name}}</li>
</ul>
v-for也可以对对象进行操作,我们把tabs改成对象模式
<!--tab标签-->
<ul>
<li v-for="(value,key) in tabs" >{{key}}---{{value}}</li>
</ul>
data:function() {
return {
tabs:{
"1":"tab1",
"2":"tab2",
"3":"tab3"
}
}
},
要特别注意:(value,key)第一个参数是value,第二是key,不能弄反了。
七、v-model
vue支持数据的双向绑定,后台数据对页面元素的绑定,可以通过声明式渲染({{text}})以及v-bind指令实现,页面元素对后台数据的绑定就需要使用v-model了。
页面能改变数据的元素包括input,textarea,select,radio,checkbox等。
<!--v-model-->
<div>msg:{{message}}</div>
<input type="text" v-model="message" >
...
data:function() {
return {
message:''
}
},
八、自定义指令
当提供的这些内置指令不满足你的要求时,用户也可自定义指令,实现对dom的底层操作。比如我们可以自定义指令实现对输入的电话,身份证等进行校验。
- 语法
自定义指令可以分为全局指令和局部指令两种。
全局指令,定义在Vue对象上:
Vue.directive('check',{
//实现各种钩子
})
局部指令
directives:{
check:{
//实现各类钩子
}
}
其中,第一个参数,"check"就是指令名,在使用时需要加上"v-"前缀,第二个参数指令的各种钩子,指令的具体实现需要在这些钩子中完成。指令的使用与原生的一致,在元素上增加"v-check"即可。
<div v-check ></div>
接下来我们看各个钩子的实现:
Vue.directive('check',{
/**
*绑定
*只调用一次,第一次绑定到元素时调用
**/
bind:function(el, binding, vnode){
console.log("bind");
},
/**
*插入
*被绑定的元素插入到父节点时调用
**/
inserted:function(el, binding, vnode){
console.log("inserted");
},
/**
*组件更新
*根据获得的新值执行对应的更新,对于初始值也会调用一次
**/
update:function(el, binding, vnode,oldVnode){
console.log("update");
},
/**
*组件更新完成
**/
componentUpdated:function(el, binding, vnode,oldVnode){
console.log("componentUpdated");
},
/**
*解绑
*只调用一次,指令与元素解绑时调用。
**/
unbind:function(el, binding, vnode){
console.log("unbind");
}
})
页面加载时执行:
bind
inserted
组件更新时执行
update
componentUpdated
卸载时执行
unbind
其入参包括:
el:所绑定的元素,可以直接进行dom操作
binding:一个对象,包含以下属性:
name:指令名,不包含v-的前缀;
value:指令的绑定值;例如:v-my-directive="1+1",value的值是2;
oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子函数中可用,无论值是否改变都可用;
expression:绑定值的字符串形式;例如:v-my-directive="1+1",expression的值是'1+1';
arg:传给指令的参数;例如:v-my-directive:foo,arg的值为 'foo';
modifiers:一个包含修饰符的对象;例如:v-my-directive.a.b,modifiers的值为{'a':true,'b':true}
vnode:Vue编译的生成虚拟节点;
oldVnode:上一次的虚拟节点,仅在update和componentUpdated钩子函数中可用。
- 实例
很多同学在实际项目中并不习惯自定义指令,因为很多时候,我们都可以找到其他的解决方案替代。那自定义指令有什么优势呢,我们看到它的钩子入参中都含有el,可以对DOM进行操作。VUE是数据驱动模式,但数据驱动并不是包治百病,有些情况下对DOM操作会更加方便。
下面我们实现一个表单数字型校验的实例,及时校验输入的字符,当非数字字符时给出提示,简便起见,将输入框颜色变红。
<!--数学校验指令-->
<div class="check">
请输入手机号:<input v-check v-model="message"></input>
</div>
Vue.directive('check',{
/**
*组件更新
*根据获得的新值执行对应的更新,对于初始值也会调用一次
**/
update:function(el, binding, vnode,oldVnode){
var numberReg=/^[1-9]\d*$/;
if (!numberReg.test(el.value)) {
//校验不通过
el.style.border="4px red solid";
} else {
//校验成功
el.style.border="none";
}
}
})
大家可以考虑下,用一个统一指令实现对身份证,手机号,银行卡的校验。