2.2 数据绑定
Vue.js作为数据驱动视图的框架,我们首先要知道的就是如何将数据在视图中展示出来,传统的Web项目中,这一过程往往是通过后端模板引擎来进行数据和视图的渲染,例如PHP的smarty, Java的velocity和freemarker。但这样导致的情况是前后端语法会交杂在一起,前端HTML文件中需要包含后端模板引擎的语法,而且渲染完成后如果需要再次修改视图,就只能通过获取DOM的方法进行修改,并手动维持数据和视图的一致。而Vue.js的核心是一个响应式的数据绑定系统,建立绑定后,DOM将和数据保持同步,这样就无需手动维护DOM,使代码能够更加简洁易懂、提升效率。
2.2.1 数据绑定语法
本小节主要介绍Vue.js的数据绑定语法,出现的例子会基于以下js代码:
var vm = new Vue({ el : '#app', data: { id : 1, index : 0, name : 'Vue', avatar : 'http://……' count : [1, 2, 3, 4, 5] names : ['Vue1.0', 'Vue2.0'], items : [ { name : 'Vue1.0', version : '1.0' }, { name : 'Vue1.1', version : '1.0' } ] } });
1.文本插值
数据绑定最基础的形式就是文本插值,使用的是双大括号标签{{}},为“Mustache”语法(源自前端模板引擎Mustache.js),示例如下:
<span>Hello {{ name }}</span> // -> Hello Vue
Vue.js实例vm中name属性的值将会替换Mustache标签中的name,并且修改数据对象中的name属性,DOM也会随之更新。在浏览器console中运行vm.name = 'Vue 1.0',输出结果为Hello Vue 1.0。
模板语法同时也支持单次插值,即首次赋值后再更改vm实例属性值不会引起DOM变化,例如以下模板在运行vm.name = 'Vue 1.0’后,依旧会输出Hello Vue:
<span>Hello {{* name }} </span> // -> Hello Vue
Vue.js 2.0去除了{{*}}这种写法,采用v-once代替。以上模板需要改写为<span v-once=”name”>{{name}}</span>。
2.HTML属性
Mustache标签也同样适用于HTML属性中,例如:
<div id="id-{{id}}"></div> // <div id="id-1"></div>
Vue.js 2.0中废弃了这种写法,用v-bind指令代替,<div v-bind:id="'id-' + id"/></div> 代替,或简写为<div :id="'id-' + id"></div>
3.绑定表达式
放在Mustache标签内的文本内容称为绑定表达式。除了直接输出属性值之外,一段绑定表达式可以由一个简单的JavaScript表达式和可选的一个或多个过滤器构成。例如:
{{ index + 1 }} // 1 {{ index == 0 ? 'a' : 'b'}} // a {{ name.split('').join('|') }} // V|u|e
每个绑定中只能包含单个表达式,并不支持JavaScript语句,否则Vue.js就会抛出warning异常。并且绑定表达式里不支持正则表达式,如果需要进行复杂的转换,可以使用过滤器或者计算属性来进行处理,以下的例子即为无效的表达式:
{{ var a = 1 }} // 无效 {{ if (ok) { return name } }} // 无效,但可以写成ok ? name : '' 或者ok && name这 样的写法
Vue.js绑定表达式warning:
4.过滤器
Vue.js允许在表达式后添加可选的过滤器,以管道符“|”指示。示例:
{{ name | uppercase }} // VUE
Vue.js将name的值传入给uppercase这个内置的过滤器中(本质是一个函数),返回字符串的大写值。同时也允许多个过滤器链式使用,例如:
{{ name | filterA | filterB }}
也允许传入多个参数,例如:
{{ name | filterA arg1 arg2}}
此时,filterA将name的值做为第一个参数,arg1, arg2做为第二、第三个参数传入过滤器函数中。最终函数的返回值即为输出结果。arg1, arg2可以使用表达式,也可以加上单引号,直接传入字符串。例如:
{{ name.split('') | limitBy 3 1 }} // ->u, e
过滤器limitBy可以接受两个参数,第一个参数是设置显示个数,第二个参数为可选,指从开始元素的数组下标。
Vue.js内置了10个过滤器,下面简单介绍它们的功能和用法。
① capitalize:字符串首字符转化成大写
② uppercase:字符串转化成大写
③ lowercase:字符串转化成小写
④ currency参数为{String}[货币符号] , {Number} [小数位],将数字转化成货币符号,并且会自动添加数字分节号。例如:
{{ amount | currency '¥' 2 }} // -> 若amount值为10000,则输出¥10,000.00
⑤ pluralize参数为{String} single, [double, triple],字符串复数化。如果接收的是一个参数,那复数形式就是在字符串末尾直接加一个“s”。如果接收多个参数,则会被当成数组处理,字符串会添加对应数组下标的值。如果字符串的个数多于参数个数,多出部分会都添加最后一个参数的值。例如:
<p v-for="c in count">{{ c | pluralize 'item' }} {{ c | pluralize 'st' 'nd' 'rd' 'th' }}</p>
输出结果:
⑥ json参数为{Number}[indent]空格缩进数,与JSON.stringify()作用相同,将json对象数据输出成符合json格式的字符串。
⑦ debounce传入值必须是函数,参数可选,为{Number}[wait],即延时时长。作用是当调用函数n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。例如:
<input v-on:keyup ="onKeyup | debounce 500"> // input元素上监听了keyup事件, 并且延迟500ms触发
⑧ limitBy传入值必须是数组,参数为{Number}limit, {Number}[offset], limit为显示个数,offset为开始显示数组下标。例如:
<div v-for="item in items | limitBy 10"></div> // items为数组,且只显示数 组中的前十个元素
⑨ filterBy传入值必须是数组,参数为{String | Function} targetStringOrFunction,即需要匹配的字符串或函数(通过函数返回值为true或false来判断匹配结果); “in”(可选分隔符); {String}[…searchKeys],为检索的属性区域。示例:
<p v-for="name in names | filterBy '1.0'">{{name}}</p> // 检索items数组中 值包含1.0的元素 <p v-for="item in items | filterBy '1.0' in 'name'">{{ item | json}}</p> // 检索items数组中元素属性name值为1.0的元素输出。检索区域也可以为数组,即in [name, version],在多个属性中进行检索
上述两个例子的输出结果为:
<p v-for="item in items | filterBy customFilter">{{ item | json}}</p> // 使用自定义的过滤函数,函数可以在选项methods中定义 methods : { customFilter : function(item) { if(item.name) return true // 检索所有元素中包含name属性的元素 } }
⑩ orderBy传入值必须是数组,参数为{String|Array|Function}sortKeys,即指定排序策略。这里可以使用单个键名,也可以传入包含多个排序键名的数组。也可以像Array.Sort()那样传入自己的排序策略函数。第二个参数为可选参数{String}[order],即选择升序或降序,order>=0为升序,order<0为降序。下面以三种不同的参数例子来说明具体的用法:
单个键名:<p v-for="item in items | orderBy 'name' -1">{{item.name}}</p> // items数组中以键名name进行降序排列 多个键名:<p v-for="item in items | orderBy [name, version] ">{{item.name}}</p> // 使用items里的两个键名进行排序 自定义排序函数: <p v-for="item in items | orderBy customOrder">{{item.name}} </p> methods: { customOrder: function (a, b) { return parseFloat(a.version) > parseFloat(b.version) // 对比item中 version的值的大小进行排序 } }
需要注意的是,Vue.js 2.0中已经去除了内置的过滤器,但也不用担心,我们会在第4章中详细说明过滤器的用法,以及如何声明自定义过滤器。而且Vue.js的社区中本身就有优秀的开源过滤器,比如处理时间的moment.js,和货币格式化处理的account.js,我们会在第8章中说明如何使用Vue.js的插件。
5.指令
Vue.js也提供指令(Directives)这一概念,可以理解为当表达式的值发生改变时,会有些特殊行为作用到绑定的DOM上。指令通常会直接书写在模板的HTML元素中,而为了有别于普通的属性,Vue.js指令是带有前缀的v-的属性。写法上来说,指令的值限定为绑定表达式,所以上述提到的JavaScript表达式及过滤器规则在这里也适用。本书会在第3章中详细讲述指令及自定义指令的作用。本节先简单介绍指令绑定数据和事件的语法。
① 参数
<img v-bind:src="avatar" />
指令v-bind可以在后面带一个参数,用冒号(:)隔开,src即为参数。此时img标签中的src会与vm实例中的avatar绑定,等同于:
<img src="{{avatar}}" />
② 修饰符
修饰符(Modifiers)是以半角句号.开始的特殊后缀,用于表示指令应该以特殊方式绑定。
<button v-on:click.stop="doClick"></button>
v-on的作用是在对应的DOM元素上绑定事件监听器,doClick为函数名,而stop即为修饰符,作用是停止冒泡,相当于调用了e. stopPropagation()。
2.2.2 计算属性
在项目开发中,我们展示的数据往往需要经过一些处理。除了在模板中绑定表达式或者利用过滤器外,Vue.js还提供了计算属性这种方法,避免在模板中加入过重的业务逻辑,保证模板的结构清晰和可维护性。
1.基础例子
var vm = new Vue({ el : '#app, data: { firstName : 'Gavin', lastName: 'CLY' } computed : { fullName : function() { // this指向vm实例 return this.firstName + ' ' + this.lastName } } }); <p>{{ firstName }}</p> // Gavin <p>{{ lastName }}</p> // CLY <p>{{ fullName }}</p> // Gavin CLY
此时,你对vm.firstName和vm.lastName进行修改,始终会影响vm.fullName。
2.Setter
如果说上面那个例子并没有体现出来计算属性的优势的话,那计算属性的Setter方法,则在更新属性时给我们带来了便利。示例:
var vm = new Vue({ el : '#el', data: { cents : 100, } computed : { price : { set : function(newValue) { this.cents = newValue * 100; }, get : function() { return (this.cents / 100).toFixed(2); } } } });
在处理商品价格的时候,后端往往会把价钱定义成以分为单位的整型,避免在处理浮点类型数据时产生的问题。而前端则需要把价钱再转成元进行展示,而且如果需要对价钱进行修改的话,则又要把输入的价格再恢复到分传给后端,很是繁琐。
而在使用Vue.js的计算属性后,我们可以将vm.cents设置为后端所存的数据,计算属性price为前端展示和更新的数据。
<p>¥ {{price}}</p> // ¥1.00
此时更改vm.price = 2, vm.cents会被更新为200,在传递给后端时无需再手动转化一遍数据。
2.2.3 表单控件
Vue.js中提供v-model的指令对表单元素进行双向数据绑定,在修改表单元素值的同时,实例vm中对应的属性值也同时更新,反之亦然。本小节会介绍主要input元素绑定v-model后的具体用法和处理方式,示例所依赖的js代码如下:
var vm = new Vue({ el : '#app', data: { message : '', gender : '', checked : '', multiChecked : [], selected : '', multiSelected : [] } });
1.Text
输入框示例,用户输入的内容和vm.message直接绑定:
<input type="text" v-model="message" /> <span>Your input is : {{ message }}</span>
2.Radio
单选框示例:
<label><input type="radio" value="male" v-model="gender ">男</lable> <label><input type="radio" value="female" v-model="gender ">女</lable> <p>{{ gender }}</p>
gender值即为选中的radio元素的value值。
3.Checkbox
Checkbox分两种情况:单个勾选框和多个勾选框。
单个勾选框,v-model即为布尔值,此时input的value并不影响v-model的值。
<input type="checkbox" v-model="checked" /> <span>checked : {{ checked }}</span>
多个勾选框,v-model使用相同的属性名称,且属性为数组。
<label><input type="checkbox" value="1" v-model=" multiChecked">1</lable> <label><input type="checkbox" value="2" v-model=" multiChecked">2</lable> <label><input type="checkbox" value="3" v-model=" multiChecked">3</lable> <p>MultiChecked: {{ multiChecked.join('|') }}</p>
4.Select
同Checkbox元素一样,Select也分单选和多选两种,多选的时候也需要绑定到一个数组。
单选:
<select v-model="selected"> <option selected>A</option> <option>B</option> <option>C</option> </select> <span>Selected: {{ selected }}</span>
多选:
<select v-model="multiSelected" multiple> <option selected>A</option> <option>B</option> <option>C</option> </select> <br> <span>MultiSelected: {{ multiSelected.join('|') }}</span>
5.绑定value
表单控件的值同样可以绑定在Vue实例的动态属性上,用v-bind实现。示例:
1.Checkbox
<input type="checkbox" v-model="checked" v-bind:true-value="a" v-bind:false-value="b">
选中:vm.checked == vm.a // -> true
未选中:vm.checked == vm.b // -> true
2.Radio
<input type="radio" v-model="checked", v-bind:value="a">
选中: vm.checked == vm.a // -> true
3.Select Options
<select v-model="selected">
<! -- 对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
选中:
typeof vm.selected // -> 'object'
vm.selected.number // -> 123
6.参数特性
Vue.js为表单控件提供了一些参数,方便处理某些常规操作。
① lazy
默认情况下,v-model在input事件中同步输入框值与数据,加lazy属性后从会改到在change事件中同步。
<input v-model="query" lazy />
② number
会自动将用户输入转为Number类型,如果原值转换结果为NaN则返回原值。
<input v-model="age" number/>
③ debounce
debounce为设置的最小延时,单位为ms,即为单位时间内仅执行一次数据更新。该参数往往应用在高耗操作上,例如在更新时发出ajax请求返回提示信息。
<input v-model="query" debounce="500" />
不过Vue.js 2.0中取消了lazy和number作为参数,用修饰符(modifier)来代替:
<input v-model.lazy="query" /> <input v-model.numer="age" />
新增了trim修饰符,去掉输入值首尾空格:
<input v-model.trim="name" />
去除了debounce这个参数,原因是无法监测到输入新数据,但尚未同步到vm实例属性时这个状态。如果仍有需要,官方提供了手动实现的例子https://jsbin.com/zefawu/3/edit?html, output。
2.2.4 Class与Style绑定
在开发过程中,我们经常会遇到动态添加类名或直接修改内联样式(例如tab切换)。class和style都是DOM元素的attribute,我们当然可以直接使用v-bind对这两个属性进行数据绑定,例如<p v-bind:style='style'><p>,然后通过修改vm.style的值对元素样式进行修改。但这样未免过于繁琐而且容易出错,所以Vue.js为这两个属性单独做了增强处理,表达式的结果类型除了字符串之外,还可以是对象和数组。本小节就会对这两个属性具体的用法进行说明。
1.Class绑定
首先说明的是class属性,我们绑定的数据可以是对象和数组,具体的语法如下:
① 对象语法:v-bind:class接受参数是一个对象,而且可以与普通的class属性共存。
<div class="tab" v-bind:calss="{'active' : active , 'unactive' : ! active}"> </div> vm实例中需要包含 data : { active : true }
渲染结果为: <div class="tab active"></div>
② 数组语法:v-bind:class也接受数组作为参数。
<div v-bind:class="[classA, classB]"></div> vm实例中需要包含 data : { classA : 'class-a', classB : 'class-b' }
渲染结果为:<div class="class-a class-b"></div>。
也可以使用三元表达式切换数组中的class, <div v-bind:class="[classA, isB ? classB :'']"></div>。如果vm.isB = false,则渲染结果为<div v-bind:class="class-a"></div>。
2.内联样式绑定
style属性绑定的数据即为内联样式,同样具有对象和数组两种形式:
① 对象语法:直接绑定符合样式格式的对象。
<div v-bind:style="alertStyle"></div> data : { alertStyle : { color : 'red', fontSize : '20px' } }
除了直接绑定对象外,也可以绑定单个属性或直接使用字符串。
<div v-bind:style="{ fontSize : alertStyle.fontSize, color : 'red'}"></div>
② 数组语法:v-bind:style允许将多个样式对象绑定到统一元素上。
<div v-bind:style="[ styleObjectA, styleObjectB]" .></div>
3.自动添加前缀
在使用transform这类属性时,v-bind:style会根据需要自动添加厂商前缀。:style在运行时进行前缀探测,如果浏览器版本本身就支持不加前缀的css属性,那就不会添加。