2.3 模板渲染
当获取到后端数据后,我们会把它按照一定的规则加载到写好的模板中,输出成在浏览器中显示的HTML,这个过程就称之为渲染。而Vue.js是在前端(即浏览器内)进行的模板渲染。本节主要介绍Vue.js渲染的基本语法。
2.3.1 前后端渲染对比
早期的Web项目一般是在服务器端进行渲染,服务器进程从数据库获取数据后,利用后端模板引擎,甚至于直接在HTML模板中嵌入后端语言(例如JSP),将数据加载进来生成HTML,然后通过网络传输到用户的浏览器中,然后被浏览器解析成可见的页面。而前端渲染则是在浏览器里利用JS把数据和HTML模板进行组合。两种方式各有自己的优缺点,需要更具自己的业务场景来选择技术方案。
前端渲染的优点在于:
① 业务分离,后端只需要提供数据接口,前端在开发时也不需要部署对应的后端环境,通过一些代理服务器工具就能远程获取后端数据进行开发,能够提升开发效率。
② 计算量转移,原本需要后端渲染的任务转移给了前端,减轻了服务器的压力。
而后端渲染的优点在于:
① 对搜索引擎友好。
② 首页加载时间短,后端渲染加载完成后就直接显示HTML,但前端渲染在加载完成后还需要有段js渲染的时间。
Vue.js 2.0开始支持服务端渲染,从而让开发者在使用上有了更多的选择。
2.3.2 条件渲染
Vue.js提供v-if, v-show, v-else, v-for这几个指令来说明模板和数据间的逻辑关系,这基本就构成了模板引擎的主要部分。下面将详细说明这几个指令的用法和场景。
1.v-if/v-else
v-if和v-else的作用是根据数据值来判断是否输出该DOM元素,以及包含的子元素。例如:
<div v-if="yes">yes</div>
如果当前vm实例中包含data.yes = true,则模板引擎将会编译这个DOM节点,输出<div>yes</div>。
我们也可以利用v-else来配合v-if使用。例如:
<div v-if="yes">yes</div> <div v-else>no</div>
需要注意的是,v-else必须紧跟v-if,不然该指令不起作用。例如:
<div v-if="yes">yes</div> <p>the v-else div shows</p> <div v-else>no</div>
最终这三个元素都会输出显示在浏览器中。
v-if绑定的元素包含子元素则不影响和v-else的使用。例如:
<div v-if="yes"> <div v-if="inner">inner</div> <div v-else>not inner</div> </div> <div v-else>no</div> new Vue({ data : { yes : true, inner : false } })
输出结果为:
<div> <div>not inner></div> </div>
2.v-show
除了v-if, v-show也是可以根据条件展示元素的一种指令。例如:
<div v-show="show">show</div>
也可以搭配v-else使用,用法和v-if一致。例如:
<div v-show="show">show</div> <div v-else>hidden</div>
与v-if不同的是,v-show元素的使用会渲染并保持在DOM中。v-show只是切换元素的css属性display。例如:
<div v-if="show">if</div> <div v-show="show">show</div>
show分别为true时的结果:
show分别为false时的结果:
3.v-if vs v-show
从上述v-show图能够明显看到,当v-if和v-show的条件发生变化时,v-if引起了dom操作级别的变化,而v-show仅发生了样式的变化,从切换的角度考虑,v-show消耗的性能要比v-if小。
除此之外,v-if切换时,Vue.js会有一个局部编译/卸载的过程,因为v-if中的模板也可能包括数据绑定或子组件。v-if会确保条件块在切换当中适当地销毁与中间内部的事件监听器和子组件。而且v-if是惰性的,如果在初始条件为假时,v-if本身什么都不会做,而v-show则仍会进行正常的操作,然后把css样式设置为display:none。
所以,总的来说,v-if有更高的切换消耗而v-show有更高的初始渲染消耗,我们需要根据实际的使用场景来选择合适的指令。
2.3.3 列表渲染
v-for指令主要用于列表渲染,将根据接收到数组重复渲染v-for绑定到的DOM元素及内部的子元素,并且可以通过设置别名的方式,获取数组内数据渲染到节点中。例如:
<ul> <li v-for="item in items"> <h3>{{item.title}}</h3> <p>{{item.description}}</p> </li> </ul> var vm = new Vue({ el : '#app', data: { items : [ { title : 'title-1', description : 'description-1'}, { title : 'title-2', description : 'description-2'}, { title : 'title-3', description : 'description-3'}, { title : 'title-4', description : 'description-4'} ] } });
其中items为data中的属性名,item为别名,可以通过item来获取当前数组遍历的每个元素,输出结果为:
<ul> <li> <h3>title-1</h3> <p>description-1</p> </li><li> <h3>title-2</h3> <p>description-2</p> </li><li> <h3>title-3</h3> <p>description-3</p> </li><li> <h3>title-4</h3> <p>description-4</p> </li> </ul>
v-for内置了$index变量,可以在v-for指令内调用,输出当前数组元素的索引。另外,我们也可以自己指定索引的别名,如<li v-for="(index, item) in items">{{index}} -{{$index}} - {{item.title}}</li>,输出结果为:
<ul> <li> 0-0- title-1 </li><li> 1-1- title-2 </li><li> 2-2- title-3 </li><li> 3-3- title-4 </li> </ul>
需要注意的是Vue.js对data中数组的原生方法进行了封装,所以在改变数组时能触发视图更新,但以下两种情况是无法触发视图更新的:
① 通过索引直接修改数组元素,例如vm.items[0] = { title : 'title-changed'};
② 无法直接修改“修改数组”的长度,例如:vm.items.length = 0
对于第一种情况,Vue.js提供了$set方法,在修改数据的同时进行视图更新,可以写成:
vm.items.$set(0, { title : 'title-changed'} 或者vm.$set('items[0]', { title : 'title-also-changed '}),这两种方式皆可以达到效果。
在列表渲染的时候,有个性能方面的小技巧,如果数组中有唯一标识id,例如:
items : [ { _id : 1, title : 'title-1'}, { _id : 2, title : 'title-2'}, { _id : 3, title : 'title-3'} … ]
通过trace-by给数组设定唯一标识,我们将上述v-for作用于的li元素修改为:
<li v-for="item in items" track-by="_id"></li>
这样,Vue.js在渲染过程中会尽量复用原有对象的作用域及DOM元素。
v-for除了可以遍历数组外,也可以遍历对象,与$index类似,作用域内可以访问另一内置变量$key,也可以使用(key, value)形式自定义key变量。
<li v-for="(key, value) in objectDemo"> {{key}} - {{$key}} : {{value}} </li> var vm = new Vue({ el : '#app', data: { objectDemo : { a : 'a-value', b : 'b-value', c : 'c-value', } } });
输出结果:
最后,v-for还可以接受单个整数,用作循环次数:
<li v-for="n in 5"> {{ n }} </li>
输出结果:
2.3.4 template标签用法
上述的例子中,v-show和v-if指令都包含在一个根元素中,那是否有方式可以将指令作用到多个兄弟DOM元素上?Vue.js提供了template标签,我们可以将指令作用到这个标签上,但最后的渲染结果里不会有它。例如:
<template v-if="yes"> <p>There is first dom</p> <p>There is second dom</p> <p>There is third dom</p> </template>
输出结果为:
同样,template标签也支持使用v-for指令,用来渲染同级的多个兄弟元素。例如:
<template v-for="item in items"> <p>{{item.name}}</p> <p>{{item.desc}}<p> </template>