唐抉的个人博客

前端框架之Vue.js(三)

字数统计: 8.4k阅读时长: 39 min
2022/11/04

Vue.js基础

列表渲染

v-for把一个数组对应为一组元素

基于一个数组来渲染列表可以使用v-for指令。v-for指令需要通过item in items形式的特殊语法,其中items是源数据数组,而item是被迭代的数组元素的别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{item.message}}
</li>
</ul>
</body>
<script>
var example1=new Vue({
el:'#example-1',
data:{
items:[
{message:'First'},
{message:'Second'}
]
}
})
</script>

运行结果如下:

v=for块中,可以访问所有父作用域的property。v-for还支持一个可选的第二个参数,即当前项的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<ul id="example-2">
<li v-for="(item,index) in items">
{{parentMessage}}-{{index}}-{{item.message}}
</li>
</ul>
</body>
<script>
var example2=new Vue({
el:'#example-2',
data:{
parentMessage:'Parent',
items:[
{message:'First'},
{message:'Second'}
]
}
})
</script>

运行结果如下:

其中,也可以用of替代in作为分隔符,if更接近Javascript迭代器的语法:

1
<div v-for="item of items"></div>

v-for里使用对象

遍历一个对象的property可以用v-for指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{value}}
</li>
</ul>
</body>
<script>
new Vue({
el:'#v-for-object',
data:{
object:{
title:'How to do lists in Vue',
author:'ZhangSan',
publicshedAt:'2022-11-04'
}
}
})
</script>

运行结果如下:

也可以提供第二个参数为property名称(即键名):

1
2
3
4
5
6
7
<body>
<ul id="v-for-object" class="demo">
<li v-for="(value,name) in object">
{{name}}:{{value}}
</li>
</ul>
</body>

运行结果如下:

还可以用第三个参数作为索引:

1
2
3
4
5
6
7
<body>
<ul id="v-for-object" class="demo">
<li v-for="(value,name,index) in object">
{{index}}:{{name}}:{{value}}
</li>
</ul>
</body>

运行结果如下:

在遍历对象时,会按Object.keys()的结果遍历,但不能保证它的结果在不同的JavaScript引擎下都一致。

维护状态

当Vue正在更新使用v-for渲染的元素列表时,默认使用“就地更新”的策略。若数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个默认模式只适用于不依赖子组件状态或临时DOM状态的列表渲染输出(如表单输入值)。

为了给Vue一个提示以便它能够跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一的key attribute:

1
2
3
<div v-for="item in items" v-bind:key="item.id">
<!--内容-->
</div>

尽可能在使用v-for时提供key attribute,除非遍历输出的DOM内容非常简单,或是刻意依赖默认行为以获取性能上的提升。由于它是Vue识别节点的一个通用机制,key并不仅与v-for特别关联。

不要使用对象或数组之类的非基本类型值作为v-forkey,要用字符串或数值类型的值。

数组更新检测

变更方法

Vue将被侦听的数组的变更方法进行了包裹,因此它们也将会触发视图更新。

这些被包裹过的方法有:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替换数组

变更方法会变更调用了这些方法的原始数组。相比之下,也有非变更方法。如filter()concat()slice()它们不会变更原始数组,而是总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组。

1
2
3
example.items=example.items.filter(function (item){
return item.message.match(/Foo/)
})

注意:由于JavaScript的限制,Vue不能检测数组和对象的变化。

显示过滤/排序后的结果

若要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据,可以通过创建一个计算属性来返回过滤或排序后的数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="test">
<li v-for="n in evenNumbers">{{n}}</li>
</div>
</body>
<script>
new Vue({
el:'#test',
data:{
numbers:[1,2,3,4,5]
},
computed:{
evenNumbers:function(){
return this.numbers.filter(function (number){
return number %2===0
})
}
}
})
</script>

注意:v-for指令不能在根节点使用!

在嵌套v-for循环中不适应计算属性的情况下,可以使用一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<div id="test">
<ul v-for="set in sets">
<li v-for="n in even(set)">{{n}}</li>
</ul>
</div>
</body>
<script>
new Vue({
el:'#test',
data:{
sets:[[1,2,3,4,5],[6,7,8,9,10]]
},
methods:{
even:function(numbers){
return numbers.filter(function (number){
return number %2===0
})
}
}
})
</script>

v-for里使用值范围

v-for也可以接收整数。在这种情况下,它会把模板重复对应次数:

1
2
3
4
5
6
7
8
9
<div id="test">
<span v-for="n in 10">{{n}}</span>
</div>
</body>
<script>
new Vue({
el:'#test'
})
</script>

<template>上是使用v-for

v-if类似,也可以利用带有v-for<template>来循环渲染一段包含多个元素的内容:

1
2
3
4
5
6
<ul>
<template v-for="item in items">
<li>{{item.msg}}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

在组件上使用v-for

在自定义组件上,可以像在任何普通元素上一样使用v-for

1
<my-component v-for="item in items" :key="item.id"></my-component>

在Vue2.2.0+的版本里,在组件上使用v-for时,key是必须的。

由于组件有自己独立的作用域,任何数据都不会被自动传递到组件里。使用prop把迭代数据传递到组件里:

1
2
3
4
<my-component v-for="(item,index) in items" 
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"></my-component>

若是自动将item注入到组件里,会使得组件与v-for的运行紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。

下面是个简单的todo列表的完整代码:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat">
<button>Add</button>
</form>
<ul>
<!--这里的is="todo-item" attribute在使用DOM模板时是十分必要的。
在<ul>元素内只有<li>元素会被看作有效内容。
这样做实现的效果与<todo-item>相同,但可以避开一些潜在的浏览器解析错误-->
<li is="todo-item" v-for="(todo,index) in todos"
v-bind:key="todo.id" v-bind:title="todo.title"
v-on:remove="todos.splice(index,1)"></li>
</ul>
</div>
</body>
<script>
Vue.component('todo-item',{
template:'\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>\
',
props:['title']
})
new Vue({
el:'#todo-list-example',
data:{
newTodoText:'',
todos:[
{
id:1,
title:'Do the dishes',
},
{
id:2,
title:'Take out the trash',
},
{
id:3,
title:'Mow the lawn'
}
],
nextTodoId:4
},
methods:{
addNewTodo:function(){
this.todos.push({
id:this.nextTodoId++,
title:this.newTodoText
})
this.newTodoText=''
}
}
})
</script>
</html>

运行结果如下:

事件处理

使用v-on监听事件

可以用v-on指令监听DOM事件,并在触发时运行一些JavaScriptdiamond:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div id="example-1">
<button v-on:click="counter+=1">Add 1</button>
<p>The button above has been clicked {{counter}} times.</p>
</div>
</body>
<script>
var example1=new Vue({
el:'#example-1',
data:{
counter:0
}
})
</script>

运行结果如下:

使用v-on指令有几个好处:

  • 看一眼HTML模板就可以定位在JavaScript代码里对应的方法
  • 由于无需在JavaScript里手动绑定事件,因此所编写的ViewModel代码是纯粹的逻辑,与DOM完全解耦,更易于测试
  • 当一个ViewModel被销毁时,所有的事件处理器都会自动被删除,无需额外清理。

事件处理方法

由于许多事件处理逻辑会更为复杂,因此直接把JavaScript代码写在v-on指令中是不可行的,v-on还可以接收一个需要调用的方法名称:

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
<body>
<div id="example-2">
<!--greet是定义的方法名-->
<button v-on:click="greet">Greet</button>
</div>
</body>
<script>
var example2=new Vue({
el:'#example-2',
data:{
name:'Vue.js'
},
//在methods对象中定义方法
methods:{
greet:function(event){
//this在方法里指向当前Vue示例
alert('Hello '+this.name+'!')
//event是原生DOM事件
if(event){
alert(event.target.tagName)
}
}
}
})
</script>

运行结果如下:

与此同时,也可以在F12的JavaScript控制台里使用example2.greet()直接调用方法。

内联处理器中的方法

除了直接绑定到一个方法,也可以在内联JavaScript语句中调用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
</body>
<script>
new Vue({
el:'#example-3',
methods:{
say:function(message){
alert(message)
}
}
})
</script>

运行结果如下:

若需要在内联语句处理器中访问原始的DOM事件,可以使用特殊变量$event把它传入方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="example-3">
<button v-on:click="warn('Form cannot be submitted yet.',$event)">
Submit</button>
</div>
</body>
<script>
new Vue({
el:'#example-3',
methods:{
warn:function(message, event){
//现在可以访问原生事件对象
if(event){
event.preventDefault()
}
alert(message)
}
}
})
</script>

运行结果如下:

事件修饰符

比在事件处理程序中调用event.preventDefault()event.stopPropagation()更好的方式是:方法只有纯粹的数据逻辑,而不是去处理DOM事件细节。

Vue.js为v-on提供了事件修饰符,修饰符由点开头的指令后缀来表示:

  • .stop:阻止事件向上级DOM元素传递
  • .prevent:阻止默认事件的发生
  • .capture:添加事件侦听器时使用事件捕获模式
  • .self:将事件绑定到自身,只有自身才能触发
  • .once:设置事件只能触发一次
  • .passive:启动被动监听器,默认行为会立即触发。添加了.passive 表示不会在监听函数里添加 preventDefault()来阻止默认行为。
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
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在event.target是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

<!--点击事件将只会触发一次-->
<a v-on:click.once="doThis"></a>

<!--滚动事件的默认行为将会立即触发,而不会等待onScroll完成-->
<div v-on:scroll.passive="onScroll">...</div>

使用修饰符时要注意顺序,相应的代码会以同样的顺序产生。因此用v-on:click.prevent.self会阻止所有的点击,而v-on:click.self.prevent只会阻止对元素本身的点击。

不像其他只能对原生的DOM事件起作用的修饰符,.once修饰符还能被用到自定义的组件事件上。

注意:不要把.passive.prevent一起使用,因为.prevent将会被忽略,同时浏览器可能会展示一个警告。.passive会告诉浏览器不阻止事件的默认行为。

按键修饰符

在监听键盘事件时,经常需要检查详细的按键。Vue允许为v-on在监听键盘事件时添加按键修饰符:

1
2
<!--处理函数vm.submit()只会在$event.key被Enter时(即按下回车键)被调用-->
<input v-on:keyup.enter="submit">

也可以直接将KeyboardEvent.key暴露的任意有效按键名转换为kebab-case来作为修饰符:

1
2
<!--处理函数只会在$event.key等于PageDown时被调用-->
<input v-on:keyup.page-down="onPageDown"

按键码

注:keyCode的事件用法已经被废弃了,可能不会被最新的浏览器支持。

使用keyCodeattribute的形式如下:

1
<input v-on:keyup.13="submit">

为了在必要的情况下支持旧浏览器,Vue提供了绝大多数常用的按键码别名:

  • 回车:.enter
  • 换行:.tab
  • 删除:.delete(捕获删除和退格键)
  • 退出:.esc
  • 空格:.space
  • 上:.up
  • 下:.down
  • 左:.left
  • 右:.right

有一些按键(如.esc以及所有的方向键)在IE9中有不同的key值。若想要支持IE9,首选这些内置的别名。

除此之外,还可以通过全局config.keyCodes对象自定义按键修饰符别名:

1
2
//可以使用v-on:keyup.f1
Vue.config.keyCodes.f1=112

系统修饰键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器:

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。

1
2
3
4
5
<!-- Alt+C -->
<input v-on:keyup.alt.76="clear">

<!-- Ctrl+Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

修饰键与常规按键不同,在和keyup事件一起用时,事件触发时修饰键必须处于按下状态。即只有在按住ctrl的情况下释放其他按键,才会触发keyup.ctrl。而单单释放ctrl也不会触发事件。若想这样做,请为ctrl换用keyCode:keyup.17

.exact修饰符

使用.exact修饰符可以精确地控制由系统修饰符组合触发的事件:

1
2
3
4
5
6
7
8
<!-- 非ctrl独有,即使是Alt和Shift一起按下时,也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 当且仅当Ctrl被按下时才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下时才触发 -->
<button v-on:click.exact="onClick">A</button>

鼠标按钮修饰符

以下修饰符会限制处理函数仅响应特定的鼠标按钮:

  • 鼠标左键:.left
  • 鼠标右键:.right
  • 鼠标中键:.middle

表单输入绑定

v-model基础用法

可以使用v-model指令在表单<input><textarea><select>元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model还可以在自定义的输入组件中使用。

v-model指令本质上是语法糖,负责监听用户的输入事件以更新数据,并对一些极端场景进行特殊处理。

v-model忽略所有表单元素的valuecheckedselected attribute 的初始值,并将Vue示例的数据作为数据来源。不过可以通过在JavaScript在组件的data选项中声明初始值。

v-model在内部为不同的输入元素使用不同的preperty并抛出不同的事件:

  • text和textarea元素使用value property和input事件
  • checkbox和radio使用checked property和change事件
  • select字段将value作为prop并将change作为事件

注意:v-model不会在输入法组合文字过程中得到更新。若想处理这个过程,可以使用input事件处理。

文本

v-model用于表单中输入文本并绑定值时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div id="example">
<input v-model="message" placeholder="edit me">
<p>Message is:{{message}}</p>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
message:''
}
})
</script>

运行结果如下:

多行文本

v-model用于表单中输入多行文本textarea并绑定值时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<div id="example">
<span>Multiline message is :</span>
<p style="white-space:pre-line;">{{message}}</p>
<br />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
message:''
}
})
</script>

运行结果如下:

注意:在文本区域插值(<textarea>{{text}}</textarea>)并不会生效,要用v-model来代替。

复选框

单个复选框,将值绑定到布尔值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div id="example">
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{checked}}</label>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
checked:''
}
})
</script>

运行结果如下:

多个复选框,将值绑定到同一个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="example">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br />
<span>Checked names:{{checkedNames}}</span>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
checkedNames:[]
}
})
</script>

运行结果如下:

单选按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="example">
<input type="radio" id="one" value="one" v-model="picked">
<label for="one">One</label>
<br />
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br />
<span>Picked:{{picked}}</span>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
picked:''
}
})
</script>

运行结果如下:

选择框

需要单选时代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="example">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected:{{selected}}</span>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
selected:''
}
})
</script>

运行结果如下:

v-model表达式的初始值未能匹配任何选项,<select>元素将被渲染为未选中的状态。在iOS中这会使用户无法选择第一个选项。因此推荐提供一个值为空的禁用选项。

多选时即将值绑定到一个数组中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<div id="example">
<select v-model="selected" multiple style="width:50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected:{{selected}}</span>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
selected:[]
}
})
</script>

运行结果如下:

配合v-for渲染的动态选项,其代码如下:

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="example">
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{option.text}}
</option>
</select>
<span>Selected:{{selected}}</span>
</div>
</body>
<script>
new Vue({
el:'#example',
data:{
selected:'A',
options:[
{text:'One',value:'A'},
{text:'Two',value:'B'},
{text:'Three',value:'C'}
]
}
})
</script>

运行结果如下:

值绑定

对于单选按钮、复选框及选择框的选项,v-model绑定的值通常是静态字符串(对于复选框来说是布尔值):

1
2
3
4
5
6
7
8
9
10
<!-- 当选中时,picked字符串为a -->
<input type="radio" v-model="picked" value="a">

<!-- toggle为true或false -->
<input type="checkbox" v-model="toggle">

<!-- 当选中第一个选项时,selected为字符串abc -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

当想把值绑定到Vue实例的一个动态property上时,可以使用v-bind实现,且这个property的值可以不是字符串。

复选框

1
2
3
4
5
6
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">

<!-- 当选中时 -->
vm.toggle==='yes'
<!-- 当没有选中时 -->
vm.toggle==='no'

由于浏览器在提交表单时并不会包含未被选中的复选框,因此这里的true-valuefalse-value attribute并不会影响输入控件的value attribute。若要确保表单中这两个值中的一个能够被提交,则要换用单选按钮。

单选按钮

1
2
3
4
<input type="radio" v-model="pick" v-bind:value="a">

<!-- 当选中时 -->
vm.pick===vm.a

选择框的选项

1
2
3
4
5
6
7
8
9
<select v-model="selected">
<option v-bind:value="{number:123}">123</option>
</select>

<!-- 当选中时 -->
typeof vm.selected
<!-- vm.selected的类型为object-->
vm.selected.number
<!-- selected.number的值为123-->

修饰符

.lazy

在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。若将其转为在change事件之后进行同步,可以添加.lazy修饰符:

1
2
<!--在change时更新,而不是在input时-->
<input v-model.lazy="msg">

.number

即使在type="number"时,HTML输入元素的值也总会返回字符串。若这个值无法被parseFloat()解析,则会返回原始的值。

若想自动将用户的输入值转为数值类型,可以给v-model添加number修饰符:

1
<input v-model.number="age" type="number">

.trim

若想要自动过滤用户输入的首尾空白字符,可以给v-model添加trim修饰符:

1
<input v-model.trim="msg">

组件基础

基本实例

一个Vue组件的实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="example">
<button-counter></button-counter>
</div>
</body>
<script>
//定义了一个名为button-counter的新组件
Vue.component('button-counter',{
data:function(){
return {
count:0
}
},
template:'<button v-on:click="count++">You clicked me {{count}} times.</button>'
})
new Vue({
el:'#example'
})
</script>

因为组件是可复用的Vue实例,所有它与new Vue接收相同的选项,如datacomputedwatchmethods以及生命周期钩子等,像el这种根特例特有的选项除外。

组件的复用

组件可以进行任意次数的复用:

1
2
3
4
5
6
7
8
<body>
<div id="example">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
</body>

由于每用一次组件,就由一个组件的新实例被创建,因此当点击按钮时,每个组件都会各自独立维护自己的count

data必须是一个函数

在定义组件的过程中,data并不是直接提供一个对象或是变量的值。组件的data选项必须是一个函数,因此每个实例可以维护一份独立的被返回的拷贝:

1
2
3
4
5
data:function(){
return {
count:0
}
}

组件的组织

通常一个应用会以一颗嵌套的组件树的形式来组织,一个应用由各类组件组成。

为了能在模板中使用,这些组件必须先注册以便Vue能够识别。

组件的注册类型有两种:全局注册和局部注册。

组件通过Vue.component来进行全局注册:

1
2
3
Vue.component('component-name',{
//options
})

全局注册的组件可以用在其被注册之后的任何通过new Vue新创建的Vue根实例,包括其组件树种的左右子组件的模板。

通过Prop向子组件传递数据

通过Prop可以在组件上注册一些自定义attribute。当一个值传递给一个prop attribute时会变成那个组件实例的一个property。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<div id="example">
<post-counter title="Blogging with Vue"></post-counter>
<post-counter title="Hello world"></post-counter>
<post-counter title="Zhangsan"></post-counter>
<post-counter title="My journey with Vue"></post-counter>
</div>
</body>
<script>
Vue.component('post-counter',{
props:['title'],
template:'<h3>{{title}}</h3>'
})
new Vue({
el:'#example'
})
</script>

一个组件默认可以拥有任意数量的prop,任何值都可以传递给任何prop。

一个prop被注册后,就可以把数据作为一个自定义attribute传递到子组件中。

与此同时,也可以使用v-bind来动态传递prop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<div id="example">
<post-counter v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title"></post-counter>
</div>
</body>
<script>
Vue.component('post-counter',{
props:['title'],
template:'<h3>{{title}}</h3>'
})
new Vue({
el:'#example',
data:{
posts:[
{id:1,title:'Blogging with Vue'},
{id:2,title:'Hello world'},
{id:3,title:'Zhangsan'},
{id:4,title:'My journey with Vue'}
]
}
})
</script>

单个根元素

当构建一个post-counter组件时,模板里最终包含的东西远不止一个标题信息,至少会包含正文的信息:

1
2
<h3>{{title}}</h3>
<div v-html="content"></div>

但在模板中这样写时,Vue会显示一个错误说每个组件必须只有一个根元素。因此需要将模板中的内容包裹在一个父元素<div>内,如:

1
2
3
4
<div class="post-counter">
<h3>{{title}}</h3>
<div v-html="content"></div>
</div>

当不满足于标题和内容信息时,组件会变得越来越复杂。此时为每个相关的信息都定义一个prop会变得很麻烦。故此时需要重构组件,让组件变成接收一个单独的post prop:

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
<body>
<div id="example">
<post-counter
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"></post-counter>
</div>
</body>
<script>
Vue.component('post-counter',{
props:['post'],
template:`
<div class="post-counter">
<h3>{{post.title}}</h3>
<div v-html="post.content"></div>
</div> `
})
new Vue({
el:'#example',
data:{
posts:[
{id:1,title:'Blogging with Vue'},
{id:2,title:'Hello world'},
{id:3,title:'Zhangsan'},
{id:4,title:'My journey with Vue'}
]
}
})
</script>

无论何时为post对象添加新的property,都能自动地在重构后的组件内可用。

监听子组件事件

在开发组件时,其一些功能可能要求和父级组件进行沟通。如引入一个辅助功能来放大博文部分的字号,同时让页面的其他部分保持默认的字号。

则可以在父组件中添加一个postFontSize数据 property:

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
<body>
<div id="example">
<!-- 在模板中用来控制所有博文的字号 -->
<div :style="{fontSize:postFontSize+'em'}">
<!-- 增加监听器,父级组件可通过v-on监听子组件实例的事件,可接收该事件并刷新对应的值 -->
<post-counter
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="postFontSize+=0.1"
></post-counter>

</div>
</div>
</body>
<script>
Vue.component('post-counter',{
props:['post'],
//添加一个放大字号的按钮,通过调用$emit方法并传入事件名称类触发一个事件
template:`
<div class="post-counter">
<h3>{{post.title}}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div> `
})
new Vue({
el:'#example',
data:{
posts:[
{id:1,title:'Blogging with Vue'},
{id:2,title:'Hello world'},
{id:3,title:'Zhangsan'},
{id:4,title:'My journey with Vue'}
],
postFontSize:1//添加postFontSize数据property
}
})
</script>

使用事件抛出一个值

用一个事件来抛出一个特定的值是非常实用的。如上面例子中,想让post-counter组件自行决定它的文本要放大多少,就是使用$emit的第二个参数来提供这个值:

1
2
3
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>

当父级组件监听这个事件时,可以通过$emit访问到被抛出的这个值:

1
v-on:enlarge-text="postFontSize+=$event"

若事件处理函数是一个方法,则这个值将会作为第一个参数传入这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
<post-counter 
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="onEnlargeText"
></post-counter>

methods:{
onEnlargeText:function(enlargeAmount){
this.postFontSize+=enlargeAmount
}
}

在组件上使用v-model

自定义事件也可以用于创建支持v-model的自定义输入组件。

1
2
3
4
5
6
<input v-model="searchText">
<!-- 等价于 -->
<input
v-bind:value="searchText"
v-on:input="searchText=$event.target.value"
>

当用在组件上时,v-model则会变成这样:

1
2
3
4
<custom-input 
v-bind:value="searchText"
v-on:input="searchText=$event"
></custom-input>

为了使v-model能够正常工作,这个组件内的<input>必须将其value attribute绑定到名叫value的prop上,并在其input事件被触发时,将新的值通过自定义的input事件抛出,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<!--调用组件-->
<custom-input v-model="searchText"></custom-input>
</body>
<script>
Vue.component('custom-input',{
props:['value'],
template:`
<input
v-bind:value="value"
v-on:input="$emit('input',$event.target.value)"
>
`
})
</script>

通过插槽分发内容

Vue自定义的<slot>元素可以向一个组件传递内容,只要在需要的地方加入插槽就行:

1
2
3
4
5
6
7
8
Vue.component('alert-box',{
template:`
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

动态组件

可以通过Vue的<component>元素加一个特殊的is attribute实现在不同组件之间进行动态切换,如在一个多标签的页面里实现无跳转切换标签:

1
2
<!-- 组件会在currentTabComponent改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

其中,currentTabComponent可以包括已注册组件的名字或是一个组件的选项对象。

这个attribute可以用于常规HTML元素,但这些元素将被视为组件,即所有的attribute都会作为DOM attribute被绑定。若想让其正常工作,则需要使用.prop修饰器。

解析DOM模板时的注意事项

对于一些HTML元素如<ul><ol><table><select>,哪些可以出现在其内部是由严格限制的。而另一些HTML元素如<li><tr><option>,只能出现在其其他某些特定的元素内部。

1
2
3
4
5
6
7
8
9
<!--自定义组件blog-post-row会被作为无效内容-->
<table>
<blog-post-row></blog-post-row>
</table>

<!--使用is attribute可以让自定义组件不会被作为无效内容-->
<table>
<tr is="blog-post-row"></tr>
</table>

若是从以下来源使用模板则没有这个限制:

  • 字符串
  • 单文件组件(.vue
  • <script type="text/x-template">
CATALOG
  1. 1. Vue.js基础
    1. 1.1. 列表渲染
      1. 1.1.1. 用v-for把一个数组对应为一组元素
      2. 1.1.2. 在v-for里使用对象
      3. 1.1.3. 维护状态
      4. 1.1.4. 数组更新检测
        1. 1.1.4.1. 变更方法
        2. 1.1.4.2. 替换数组
      5. 1.1.5. 显示过滤/排序后的结果
      6. 1.1.6. 在v-for里使用值范围
      7. 1.1.7. 在<template>上是使用v-for
      8. 1.1.8. 在组件上使用v-for
    2. 1.2. 事件处理
      1. 1.2.1. 使用v-on监听事件
      2. 1.2.2. 事件处理方法
      3. 1.2.3. 内联处理器中的方法
      4. 1.2.4. 事件修饰符
      5. 1.2.5. 按键修饰符
        1. 1.2.5.1. 按键码
      6. 1.2.6. 系统修饰键
        1. 1.2.6.1. .exact修饰符
        2. 1.2.6.2. 鼠标按钮修饰符
    3. 1.3. 表单输入绑定
      1. 1.3.1. v-model基础用法
        1. 1.3.1.1. 文本
        2. 1.3.1.2. 多行文本
        3. 1.3.1.3. 复选框
        4. 1.3.1.4. 单选按钮
        5. 1.3.1.5. 选择框
      2. 1.3.2. 值绑定
        1. 1.3.2.1. 复选框
        2. 1.3.2.2. 单选按钮
        3. 1.3.2.3. 选择框的选项
      3. 1.3.3. 修饰符
        1. 1.3.3.1. .lazy
        2. 1.3.3.2. .number
        3. 1.3.3.3. .trim
    4. 1.4. 组件基础
      1. 1.4.1. 基本实例
      2. 1.4.2. 组件的复用
        1. 1.4.2.1. data必须是一个函数
      3. 1.4.3. 组件的组织
      4. 1.4.4. 通过Prop向子组件传递数据
      5. 1.4.5. 单个根元素
      6. 1.4.6. 监听子组件事件
        1. 1.4.6.1. 使用事件抛出一个值
        2. 1.4.6.2. 在组件上使用v-model
      7. 1.4.7. 通过插槽分发内容
      8. 1.4.8. 动态组件
      9. 1.4.9. 解析DOM模板时的注意事项