唐抉的个人博客

前端框架之Vue.js(二)

字数统计: 6k阅读时长: 27 min
2022/11/04

Vue.js基础

模板语法

Vue.js使用了基于HTML的模板语法,允许开发者声明式地将DOM绑定至底层Vue实例的数据。所有的Vue.js模板都是合法的HTML。

插值

文本

数据绑定最常见的形式就是使用"Mustache"语法(双大括号)的文本插值:

1
2
3
<div id="app">
<span>message:{{message}}</span>
</div>
1
2
3
4
5
6
var app=new Vue({
el:'#app',
data:{
message:'Hello Vue!'
}
})

Mustache标签将会被替代为对应数据对象上msg property的值。当绑定的数据对象上msg property发生了改变,插值的内容都会更新。

使用v-once指令,可以执行一次性地插值。当数据改变时,插值处的内容不会更新,但这会影响到该节点上的其他数据绑定:

1
2
3
<div id="app">
<span v-once>本句话将不会改变:{{msg}}</span>
</div>
1
app.message='nihao'

原始HTML

双大括号会将数据解释为普通文本,并非是HTML代码。使用v-html指令可以输出HTML代码:

1
2
<p>Using mushaches:{{rewHtml}}</p>
<p>Using v-html directive:<span v-html="rewHtml"></span></p>
1
2
3
4
5
6
7
var app=new Vue({
el:'#app',
data:{
message:'Hello Vue!',
rewHtml:'<span style="color:red">This should be red.</span>'
}
})

这个span的内容将会被替换成为property值rawHtml,忽略解析property值中的数据绑定后,直接作为HTML。注意不能使用v-html来复合局部模板。

在站点上动态渲染任意的HTML可能会非常危险,其很容易导致XSS攻击。因此要只对可信内容使用HTML插值,不要对用户提供的内容使用插值。

Attribute

Mustache语法不能作用在HTML attribute上,此时应该使用v-bind指令:

1
2
3
<div v-bind:id="dynamicId">
<button v-bind:disabled="isButtonDisabled">Button</button>
</div>
1
2
3
4
5
6
var app=new Vue({
el:'#dynamicId',
data:{
isButtonDisabled:true
}
})

isButtonDisabled的值是nullundefinedfalse,则disableattribute是不会被包含在渲染出来的<button>元素中。

使用JavaScript表达式

对于所有的数据绑定,Vue.js提供了完全的JavaScript表达式支持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<div id="app">
<p>number:{{number+1}}</p>
<p>ok:{{ok?'YES':'NO'}}</p>
<p>{{message.split('').reverse().join('')}}</p>
</div>
</body>
<script>
var app=new Vue({
el:'#app',
data:{
number:123,
ok:'1>2',
message:'hello',
}
})
</script>xxxxxxxxxx17 1<body>2    <div id="app">3        <p>number:{{number+1}}</p>4        <p>ok:{{ok?'YES':'NO'}}</p>5        <p>{{message.split('').reverse().join('')}}</p>6    </div>7</body>8<script>9    var app=new Vue({10        el:'#app',11        data:{12            number:123,13            ok:'1>2',14            message:'hello',15       }16   })17</script>

上述表达式会在所属Vue实例的数据作用域下作为JavaScript被解析。注意:每个绑定都只能包含单个表达式。因此下面的例子不会生效:

1
2
3
4
<!--这是语句,不是表达式-->
{{var a=1}}
<!--流控制也不会生效,请使用三元表达式-->
{{if (ok){ return message }}}

模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如Math和Date。不要在模板表达式中视图访问用户定义的全局变量。

指令

指令是带有v-前缀的特殊attribute。指令attribute的值预期是单个JavaScript表达式v-for除外)指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM,如:

1
<p v-if="seen">现在可以看到我</p>

在这里,v-if指令将根据表达式seen值的真假来插入/移除<p>元素。

参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。如v-bind指令可以用于响应式地更新HTML attribute:

1
<a v-bind:href="url">...</a>

在这里,href是参数,告知v-bind指令将该元素的href attribute与表达式url的值绑定。

另一个v-on指令可用于监听DOM事件:

1
<a v-on:click="doSomething">...</a>

在这里参数是监听的事件名。

动态参数

从Vue2.6.0开始,可以用方括号括起来的JavaScript表达式作为一个指令的参数:

1
<a v-bind:[attributeName]="url">...</a>

这里的attributeName会被作为一个JavaScript表达式进行动态求值,求得的值将会作为最终的参数来使用。如,若Vue实例中有一个名为attributeNamedataproperty,其值为href,则上面的语句等价于v-bind:href="url"

动态参数可以为一个动态的事件名绑定处理函数:

1
<a v-on:[eventName]="doSomething">...</a>

在这里,当eventName的值为"focus"时,v-on:{eventName}将等价于v-on:focus

对动态参数值的约束

动态函数预期会求出一个字符串,异常情况下值为null。这个特殊的null值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。

对动态参数表达式的约束:

动态参数表达式有一些语法约束,如空格和引号,放在HTML attribute名里是无效的。因此使用没有空格或引号的表达式,或使用计算属性来替代这种复杂表达式。

在DOM中使用模板时(即直接在HTML文件里撰写模板),由于浏览器会把attribute名全部强制转为小写,还要避免使用大写字符来命名键名。

修饰符

修饰符是以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。如.prevent修饰符告诉v-on指令对于触发的事件调用event.preventDefault()

1
<form v-on:submit.prevent="onSubmit">...</form>

缩写

v-前缀作为一种视觉提示,用来识别模板中Vue特定的attribute。v-前缀有助于为现有标签添加动态行为,但在一些频繁用到的指令以及构建由Vue管理所有模板的单页面应用程序里,v-前缀的作用不大。

v-bind缩写

1
2
3
4
5
6
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 -->
<a :[key]="url">...</a:>

v-on缩写

1
2
3
4
5
6
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 -->
<a @[event]="doSomething">...</a:>

计算属性和侦听器

计算属性

对于任何复杂逻辑,应当使用计算属性。

基础例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="example">
<p>Original message:"{{message}}"</p>
<p>Computed reversed message:"{{reversedMessage}}"</p>
</div>
</body>
<script>
var vm=new Vue({
el:'#example',
data:{
message:'Hello'
},
computed:{
reversedMessage:function(){
return this.message.split('').reverse().join('')
}
}
})
</script>

在这里声明了一个计算属性reversedMessage,提供的函数将用作propertyvm.reversedMessage的getter函数。

打开F12中的JavaScript控制台,输入以下JavaScript代码:

1
2
3
4
5
6
console.log(vm.reversedMessage)
//返回olleH
vm.message='Goodbye'
//返回'Goodbye'
console.log(vm.reversedMessage)
//返回eybdooG

可以看到,vm.reversedMessage的值始终取决于vm.message的值。

vm.reversedMessage依赖于vm.message,因此当vm.message发生改变时,所有依赖vm.reversedMessage的绑定也会更新。由于已经以声明的方式创建了这种依赖关系,因此计算属性的getter函数是没有副作用的。

计算属性缓存 vs 方法

通过在表达式中调用方法也可以达到与基础例子同样的效果,将同一函数定义为一个方法而不是一个计算属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<div id="example">
<p>Reverssed message:"{{reversedMessage()}}"</p>
</div>
</body>
<script>
var vm=new Vue({
el:'#example',
data:{
message:'hello'
},
methods:{
reversedMessage:function(){
return this.message.split('').reverse().join('')
}
}
})
</script>

打开F12中的JavaScript控制台,输入以下JavaScript代码:

1
2
3
4
5
6
console.log(vm.reversedMessage())
//返回olleh
vm.message='exam'
//返回'exam'
console.log(vm.reversedMessage())
//返回maxe

可以看到这两种方式的最终结果是相同的。不同的是计算属性是基于响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。也就是说,只要message还没有发生改变,多次访问reversedMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。但这也意味着下面的计算属性将不再更新,因为其不是响应式依赖:

1
2
3
4
5
computed:{
now:function(){
return Date.now()
}
}

而每次触发重新渲染时,调用方法总会再次执行函数。

计算属性 vs 侦听属性

Vue的侦听属性可以用来观察和响应Vue实例上的数据变动。当有一些数据需要随着其他数据变动而变动时,很容易会滥用watch。通常更好的做法是使用计算数学而不是命令是的watch回调。

watch回调版本的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div id="demo">
{{fullName}}
</div>
</body>
<script>
var vm=new Vue({
el:'#demo',
data:{
firstName:'Foo',
lastName:'Bar',
fullName:'Foo Bar'
},
watch:{
firstName:function(val){
this.fullName=val+' '+this.lastName
},
lastName:function(val){
this.fullName=this.firstName+' '+val
}
}
})
</script>

计算属性版本的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="demo">
{{fullName}}
</div>
</body>
<script>
var vm=new Vue({
el:'#demo',
data:{
firstName:'Foo',
lastName:'Bar'
},
computed:{
fullName:function(){
return this.fullName=this.firstName+' '+this.lastName
}
}
})
</script>

由此可见计算属性版本的代码比watch回调版本的代码好多了。

计算属性的setter

计算属性默认只有getter,但在需要时也可以提供一个setter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
computed:{
fullName:{
//getter
get:function(){
return this.firstName+' '+this.lastName
},
//setter
set:function(newValue){
var names=newValue.split(' ')
this.firstName=names[0]
this.lastName=names[names.length-1]
}
}
}

打开F12中的JavaScript控制台,运行vm.fullName='zhangsan'时,setter会被调用,vm.firstNamevm.lastName也会随之更新。

侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。当需要在数据变化时执行异步或开销较大的操作时,自定义侦听器的方式是最有用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
</head>
<body>
<div id="watch-example">
<p>Ask a yes/no question:
<input v-model="question">
</p>
<p>{{answer}}</p>
</div>
</body>
<script>
var watchExampleVM=new Vue({
el:'#watch-example',
data:{
question:'',
answer:'I cannot give you an answer until you ask a question!'
},
watch:{
question:function(newQuestion,oldQuestion){
this.answer='Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created:function(){
this.debouncedGetAnswer=_.debounce(this.getAnswer,500)
},
methods:{
getAnswer:function(){
if(this.question.indexOf('?')===-1){
this.answer='Questions usually contain a question mark. ;-)'
return
}
this.answer='Thinking...'
var vm=this
axios.get('https://yesno.wtf/api')
.then(function(response){
vm.answer=_.capitalize(response.data.answer)
})
.catch(function(error){
vm.answer='Error! Could not reach the API. '+error
})
}
}
})
</script>
</html>

在这个示例中,使用watch选项执行异步操作(访问一个API),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

Class与Style绑定

操作元素的class列表和内联样式是数据绑定的常见需求。由于它们都是attribute,因此可以用v-bind来处理它们。将v-bind用于classstyle时,表达式结果的类型除了字符串之外,还可以是对象或数组。

绑定HTML Class

对象语法

动态地切换class可以传给v-bind:class一个对象:

1
<div v-bind:class="{active:isActive}">Hello Vue!</div>

上面的代码表示active这个class存在与否将取决于数据property isActive的truthiness。

可以在对象中传入更多字段来动态切换多个class。v-bind:class指令也可以与普通的class attribute共存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div
class='static'
v-bind:class="{active:isActive,'text-danger':hasError}"
>Hello Vue!</div>
</body>
<script>
var tes=new Vue({
data:{
isActive:true,
hasError:false
}
})
</script>

渲染后就等价于

1
<div class='static active'>Hello Vue!</div>

isActive或者hasError变化时,class列表将相应地更新。若hasError的值为true,则class列表将变为class='static active text-danger'

绑定的数据对象不必内联定义在模板里:

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
<div v-bind:class="classObject">Hello Vue!</div>
</body>
<script>
var tes=new Vue({
data:{
classObject:{
active:true,
'text-danger':false
}
}
})
</script>

其渲染结果和上面是一样的。也可以在这里绑定一个返回对象的计算属性,这是一个常用的模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id='test' v-bind:class="classObject">Hello Vue!</div>
</body>
<script>
var tes=new Vue({
data:{
isActive:true,
error:null
},
computed:{
classObject:function(){
return {
active:this.isActive && !this.error,
'text-danger':this.error && this.error.type==='fatal'
}
}
}
})
</script>

数组语法

应用一个class列表可以把一个数组传给v-bind:class

1
2
3
4
5
6
7
8
9
10
11
<body>
<div v-bind:class="[activeClass, errorClass]">Hello Vue!</div>
</body>
<script>
var tes=new Vue({
data:{
activeClass:'active',
errorClass:'text-danger'
}
})
</script>

渲染后就等价于

1
<div class='active text-danger'>Hello Vue!</div>

若想根据条件切换列表中的class,可以用三元表达式:

1
<div v-bind:class="[isActive ? activeClass:'',errorClass]">Hello Vue!</div>

这样便是始终添加errorClass,但只有在isActive是truthy时才添加activeClass

注意:truthy是指再布尔值上下文中,转换后的值为'true',其不是true。

当有多个条件class时这样写有些繁琐,因此在数组语法中也可以使用对象语法:

1
2
<div v-bind:class="[{active:isActive},errorClass]">Hello Vue!
</div>

用在组件上

当在一个自定义组件上使用class property时,这些class将被追加到该组件的根元素上面,已存在的class不会被覆盖。

若这有一组件声明如下:

1
2
3
Vue component('my-component',{
template:'<p class="foo bar">Hi</p>'
})

使用它时添加一些class:

1
<my-component class="text"></my-component>

HTML将被渲染为:

1
2
3
<p class="foo bar text">
Hi
</p>

对于带数据绑定的class也同样适用:

1
2
3
4
5
<my-component v-bind:class="{active:isActive}"></my-component>
<!--当isActive为truthy时,HTML将被渲染为-->
<p class="foo far active">
Hi
</p>

绑定内联样式

对象语法

v-bind:style的对象语法看起来非常像CSS,但实际上是个JavaScript对象。CSS property名可以用驼峰式(camelCase)或短横线分隔(kebab-case,记得用括号括起来)来命名:

1
2
3
4
5
6
7
8
9
10
11
<body>
<div id='test' v-bind:style="{color:activeColor,fontSize:fontSize+'px'}">Hello Vue!</div>
</body>
<script>
var tes=new Vue({
el:'#test',
data:{
activeColor:'red',
fontSize:30
}
})

通常直接绑定到一个样式对象,让模板更清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div id='test' v-bind:style="styleObject">Hello Vue!</div>
</body>
<script>
var tes=new Vue({
el:'#test',
data:{
styleObject:{
color:'red',
fontSize:'13px'
}
}
})
</script>

对象语法常会结合返回对象的计算熟悉使用。

数组语法

v-bind:style的数组书法可以将多个样式对象应用到同一个元素上:

1
<div id='test' v-bind:style="[baseStyles,overridingStyles]">Hello Vue!</div>

自动添加前缀

v-bind:style需要使用添加浏览器引擎前缀的CSS property时,如tranformVue.js会自动侦测并添加相应的前缀。

多重值

从Vue2.3.0版本起,可以为style绑定中的property提供一个包含多个值的数组,常用于提供多个带前缀的值:

1
<div :style="{display:['-webkit-box','-ms-flexbox','flex']}">Helo</div>

上述代码只会渲染数组中最后一个被浏览器支持的值。若浏览器支持不带浏览器前缀的flexbox,那么就只会渲染display:flex

条件渲染

v-if

v-if指令用于条件性地渲染一块内容。这块内容只会再指令的表达式返回truthy值时被渲染:

1
<h1 v-if="awesome">Vue is awesome!</h1>

也可以用v-else添加一个"else"块:

1
2
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no!!!</h1>

<template>元素上使用v-if条件渲染分组

由于v-if是一个指令,所以必须将它添加到一个元素上。若想要切换多个元素时,可以把一个<template>元素当作不可见的包裹元素并在<template>元素上使用v-if,最终的渲染结果将不包含<template>

1
2
3
4
5
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>paragraph 2</p>
</template>

v-else

可以用v-else指令来表示v-if"else"块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id="test">
<div v-if="Math.random()>0.5">
Now you see me!
</div>
<div v-else>
Now you can not see me!
</div>
</div>
</body>
<script>
var tes=new Vue({
el:'#test'
})
</script>

v-else元素必须紧跟在带v-ifv-else-if的元素后面,否则它将不会被之别。

v-else-if

v-else-if指令表示v-if"else-if"块,可以连续使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<div id="test">
<div v-if="type==='A'">A</div>
<div v-else-if="type==='B'">B</div>
<div v-else-if="type==='c'">C</div>
<div v-else>Not A/B/c</div>
</div>
</body>
<script>
var tes=new Vue({
el:'#test',
data:{
type:'D'
}
})
</script>

类似于v-elsev-else-if也必须紧跟在带v-ifv-else-if的元素之后。

key管理可复用的元素

Vue通常会复用已有元素而不是从头开始渲染。这样除了让Vue变得非常快之外,还有其他一些好处。如允许用户在不同的登录方式之间切换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>
<div id="test">
<template v-if="loginType==='username'">
<label>Username</label>
<input placeholder="Enter your username" >
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" >
</template>
<button v-on:click="changeMessage">Toggle login type</button>
</div>
</body>
<script>
var tes=new Vue({
el:'#test',
data:{
loginType:'zhangsan'
},
methods:{
changeMessage:function(){
return this.loginType=this.loginType==='username'?'eamil':'username'
}
}
})
</script>

运行结果如下:

在输入框输入一些文本,然后按下切换按钮。可以看到用户输入在输入框的内容是不变的。

这意味着在上面的代码中切换loginType将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>不会被替换掉,仅只是替换了它的placeholder

Vue还提供一种方式来表达“这两个元素是完全独立的,不要复用它们”,只需要添加一个具有唯一值的key attribute即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>
<div id="test">
<template v-if="loginType==='username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
<button v-on:click="changeMessage">Toggle login type</button>
</div>
</body>
<script>
var tes=new Vue({
el:'#test',
data:{
loginType:'zhangsan'
},
methods:{
changeMessage:function(){
return this.loginType=this.loginType==='username'?'eamil':'username'
}
}
})
</script>

运行代码后,再次在输入框输入一些文本,然后按下切换按钮。可以看到每次切换时,输入框都将被重新渲染。

需要注意的是:这里的<label>仍然会被高效得复用,因为它们没有添加keyattribute。

v-show

另一个用于根据条件展示元素的选项是v-show指令:

1
<h1 v-show="ok">Hello!</h1>

v-if不同的是,v-show的元素始终会被渲染并保留在DOM中,其只是简单地切换元素的CSS property display

注意:v-show不支持<template>元素,也不支持`v-else

v-if VS v-show

  • v-if 是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

  • v-if 是惰性的:若在初始渲染时条件为假,则什么也不做。直至条件第一次变为真时,才会开始渲染条件块。

  • v-show不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS进行切换。

一般来说,v-if 有更高的切换开销,v-show有更高的初始渲染开销。因此若需要非常频繁地切换,则使用v-show;若在运行时条件很少改变,则使用v-if

v-ifv-for一起使用

不推荐同时使用v-ifv-for

v-ifv-for一起使用时,v-for 具有比v-if 更高的优先级。这意味着v-if将分别重复运行于每个v-for循环中。当只想为部分项渲染节点时,这种优先节点十分有用:

1
<li v-for="todo in todos" v-if="!todo.isComplete">{{todo}}</li>

上面的代码只渲染未完成的todo。

而若是想要有条件地跳过循环的执行,则可以将v-if置于外层元素(或<template>)上:

1
2
3
4
<ul v-if="todos.length">
<li v-for="todo in todos">{{todo}}</li>
</ul>
<p v-else>No todos left!</p>
CATALOG
  1. 1. Vue.js基础
    1. 1.1. 模板语法
      1. 1.1.1. 插值
        1. 1.1.1.1. 文本
        2. 1.1.1.2. 原始HTML
        3. 1.1.1.3. Attribute
        4. 1.1.1.4. 使用JavaScript表达式
      2. 1.1.2. 指令
        1. 1.1.2.1. 参数
        2. 1.1.2.2. 动态参数
        3. 1.1.2.3. 修饰符
      3. 1.1.3. 缩写
        1. 1.1.3.1. v-bind缩写
        2. 1.1.3.2. v-on缩写
    2. 1.2. 计算属性和侦听器
      1. 1.2.1. 计算属性
        1. 1.2.1.1. 基础例子
        2. 1.2.1.2. 计算属性缓存 vs 方法
        3. 1.2.1.3. 计算属性 vs 侦听属性
        4. 1.2.1.4. 计算属性的setter
      2. 1.2.2. 侦听器
    3. 1.3. Class与Style绑定
      1. 1.3.1. 绑定HTML Class
        1. 1.3.1.1. 对象语法
        2. 1.3.1.2. 数组语法
        3. 1.3.1.3. 用在组件上
      2. 1.3.2. 绑定内联样式
        1. 1.3.2.1. 对象语法
        2. 1.3.2.2. 数组语法
        3. 1.3.2.3. 自动添加前缀
        4. 1.3.2.4. 多重值
    4. 1.4. 条件渲染
      1. 1.4.1. v-if
        1. 1.4.1.1. 在<template>元素上使用v-if条件渲染分组
        2. 1.4.1.2. v-else
        3. 1.4.1.3. v-else-if
        4. 1.4.1.4. 用key管理可复用的元素
      2. 1.4.2. v-show
      3. 1.4.3. v-if VS v-show
      4. 1.4.4. v-if 与v-for一起使用