vue组件

Vue组件基础

1.1 什么是组件

组件的概念

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。

如何理解组件

简单理解,组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。

通常一个应用会以一棵嵌套的组件树的形式来组织:

img

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为什么要使用组件

举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。

而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。

1
<calendar></calendar>

我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。

有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。

####

1.2 创建组件

注册组件

我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件

1
2
3
Vue.component(componentName, {
//选项
})

对于自定义组件的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。

组件的选项

  • 与创建Vue示例时的选项相同(除了一些根级特有的选项)
  • 一个组件的 data 选项必须是一个函数 (每个组件实例具有自己的作用域,组件复用不会互相影响)
1
2
3
4
5
6
7
8
9
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

组件的使用

1
2
3
<div id="components-demo">
<button-counter></button-counter>
</div>

组件可以复用

1
2
3
4
5
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>

组件模板

每个组件模板必须只有一个根元素

模板的形式:

template 选项指定字符串 (模板字符串)

单文件组件(.vue) 最优方式

内联模板 (不推荐)

1
2
3
4
5
6
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>
X-Templates
该显示到hello-world组件上
1
2
3
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
1
2
3
Vue.component('hello-world', {
template: '#hello-world-template'
})
可以插入数据
1
2
3
4
5
6
<script type="text/x-template" id="my-panel-template">
<div class="panel">
<h2>{{ title }}</h2>
<p>{{ content }}</p>
</div>
</script>
1
2
3
4
5
6
7
8
9
10
//x-templates 模板
Vue.component('my-panel', {
data: function(){
return {
title: '我的小莉莉',
content: '啊,小莉莉,啊啊啊,啊啊啊'
}
},
template:'#my-panel-template'
});

根实例

1554129371970

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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue组件</title>
</head>
<body>
<div id="app">
<h1>{{ title }}</h1>
<hr>
<!--组件的使用-->
<my-button></my-button>
<my-button></my-button>
<my-button>小莉莉</my-button>
</div>

<script src="../dist/js/vue.js"></script>
<script>

//注册组件
Vue.component('my-button', {
data: function() {// 加function是为了独立每个组件
return {
counter: 0,
title:'hello',
message:[1,2,3,4]
}
},
methods: {
addCounter() {
this.counter ++
}
},
template: `
<button @click="addCounter">被点了{{ counter }}次</button>
`,
created() {
console.log('啊,我被创建了')
}
})


//创建根实例
let app = new Vue({
el: '#app', //根实例,挂载元素是必须的
//template: `<h2>HELLO WORLD</h2>` //如果指定了 template,挂载元素会被替换
data: {
title:"Vue组件 同志交友"
}
})
</script>
</body>
</html>

全局组件和局部组件

使用 Vue.component()注册的组件都是全局组件

我们可以定义局部组件

局部组件的命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var localTag = {
template: "<div class='sup'><div class='sub'></div></div>"
}



// 注册子组件
components: {
// 1
// "localtag": localTag
// 2
// "localTag": localTag
// 3
// "local-tag": localTag
// 4
// localTag: localTag
// 5 ES6对象语法,key value写法相同,可以省略value
localTag,
btnTag,
}

组件是局部作用域

data一定是方法不是数据需要返回值

data需要绑定方法,数据通过方法返回值进行处理,达到组件复用时,数据的私有化

每个组件都是一个独立的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var btnTag = {
// template: "<div><button>按钮1</button><button>按钮2</button></div>"
template: "<button @click='btnAction'>点击了{{ num }}下</button>",
// data需要绑定方法,数据通过方法返回值进行处理,达到组件复用时,数据的私有化
data: function() {
return {
num: 0
}
},
methods: {
btnAction: function () {
this.num++
}
}
}

全局组件附属于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
27
28
29
30
31
<body>
<div id="app">
<global-tag v-for="(o, i) in ls" :key="i"></global-tag>
</div>
</body>
<script src="js/vue-2.5.17.js"></script>
<script type="text/javascript">
// 全局组件
// 用Vue.component("组件名", {})来创建全局组件
// 全局组件附属于Vue实例,可以不需要注册就可以使用
Vue.component("global-tag", {
template: "<button @click='btnClick'>{{ n }}</button>",
data () {
return {
n: 0
}
},
methods: {
btnClick () {
this.n++
}
}
})

new Vue({
el: "#app",
data: {
ls: [0, 0, 0]
}
})
</script>

2种全局组件的区别

第一种

1554180924862

第二中对根组件使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Vue({
el:'#app',
//局部组件
components: {
'my-button': {
data:function(){+
return {
counter: 0
}
},
template: `
<button>{{counter}}</button>
`
},
'my-input': {
template:`
<input type="text">
`
}
}
})
1
2
3
4
5
6
7
8
9
10
11
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

new Vue({
el: '#app'
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})

2. 组件之间的嵌套使用和互相通信

组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。

每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。

img

父组件同绑定属性的方式进行数据传输

​ 1.给在父组件中出现的子组件名定义标签的全局属性
2.全局属性的值赋值为父组件的数据变量
3.在子组件内部,通过props拿到标签中的全局属性名

1554195726304

2.1 通过Prop向子组件传递规则

1554196714896

通过Prop向子组件传递过程

1554197586268

1554197710876

1554197759215

x-Templates

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>同志加油</title>
</head>
<body>
<div id="app"></div>

<script type="text/x-template" id="root-template">
<div class="container">
<child-component :message="message"></child-component>
</div>
</script>

<script type="text/x-template" id="child-template">
<div class="box">
<p>{{ message }}</p>
</div>
</script>

<script src="../dist/js/vue.js"></script>
<script>
//定义 注册
//组件 注册在Vue 类中
Vue.component('child-component', {
props: ['message'], //根data 中的属性 地位相同
template: '#child-template'
})


//根组件 根实例
let app = new Vue({
el:'#app',
data:{
message:"HELLO 同志",
},
template:'#root-template'
});


/*
给类添加东西
实例化 new 类 a
在给类添加东西
*/
</script>
</body>
</html>

子组件传递数据给父组件

1554201168759

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
65
66
67
68
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自组件向父组件通信</title>
<style>
p {
font-size:100px;
margin: 5px 0px;
}
</style>
</head>
<body>
<div id="app">
<h1>{{ title }}</h1>
<hr>

<p>{{ count }}</p>

<hr>

<button-count @add="addFn($event)" @del="delFn"></button-count>
</div>


<script src="../dist/js/vue.js"></script>
<script>
//注册组件
Vue.component('button-count', {
methods: {
addCount() {
//向父组件 发送事件
this.$emit('add', 2)
},
deleteCount(){
this.$emit('del', 4)
}
},
template: `
<div>
<button @click="addCount">增加2</button>
<button @click="deleteCount">减少4</button>
</div>
`,
created() {
this.$emit('add', 2)
}
})

//创建根实例 //根组件
let app = new Vue({
el: '#app',
data: {
title:'同志交友',
count: 0
},
methods: {
addFn(val){
this.count += val;
},
delFn(val) {
this.count -= val;
}
}
})
</script>
</body>
</html>

tolist

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todoList</title>
<style>
#app {
margin:100px auto 0;
width:600px;
}
.input-box {
font-size:0;
}
.input-box input {
box-sizing: border-box;
width:500px;
font-size:16px;
border:1px solid #ccc;
padding:10px;
line-height: 18px;
}
.input-box button {
width:100px;
padding:10px;
font-size:16px;
border:1px solid #ccc;
background: #f5f5f5;
cursor: pointer;
line-height: 18px;
border-left:none;
}
ul {
list-style: none;
margin:0;
padding:0;
}
.todo-list {
margin-top:20px;
}
.todo-list li{
border:1px solid #ccc;
padding:10px;
margin-bottom:10px;
font-size:0;

}
.todo-list i {
margin-right:20px;
display: inline-block;
width:16px;
height:16px;
border:1px solid #ccc;
cursor: pointer;
vertical-align: -5px;
}
.todo-list p {
width:500px;
display: inline-block;
font-size:16px;
margin:0;
line-height: 20px;
}
.todo-list span {
display:inline-block;
height:20px;
line-height: 20px;
width:40px;
cursor: pointer;
color:red;
font-size:16px;

}
.done-list {
margin-top:20px;
}
.done-list li{
border:1px solid #ccc;
padding:10px;
margin-bottom:10px;
background: #999;
color:#ccc;
cursor: not-allowed;
text-decoration: line-through;
}
.edit-input {
width:576px;
font-size:16px;
line-height: 20px;
border:1px solid #eee;
}
</style>
</head>
<body>
<div id="app">
<div class="input-box">
<input type="text" v-model.trim="newTodo" placeholder="请输入代办事项">
<button @click="addTodo">添 加</button>
</div>

<div class="todo-list">
<ul>
<li v-for="(todo,index) in todoList" :key="index">
<div v-show="isEditTodo(index)">
<input type="text" class="edit-input" :value="todo" @change="execEditTodo(index, $event)">
</div>
<div v-show="!isEditTodo(index)">
<i @click="addDone(index)"></i>
<p @dblclick="editTodoFn(index)">{{ todo }}</p>
<span @click="deleteTodo(index)">&times;</span>
</div>
</li>
</ul>
</div>

<h3>已完成</h3>
<div class="done-list">
<ul>
<li v-for="done in doneList" :key="done">{{ done }}</li>
</ul>
</div>
</div>


<script src="../dist/js/vue.js"></script>
<script>
new Vue({
el:"#app",
data: {
todoList: ['今天代码敲三遍', '晚上和小莉莉去喝酒'],
doneList: [],
newTodo:'',
editTodo: null
},
methods: {
addTodo() {
//如果输入框是空的,不执行
if (this.newTodo.length === 0) {
return;
}
//添加内容到 代办事项
this.todoList.push(this.newTodo)
//清空输入框
this.newTodo = '';
},
deleteTodo(index) {
this.todoList.splice(index, 1)
},
addDone(index) {
//把内容添加到 doneList
this.doneList.push(this.todoList[index])
//从todoList删掉
this.deleteTodo(index);
},
isEditTodo(index){
return this.editTodo === index;
},
editTodoFn(index) {
this.editTodo = index;
},
execEditTodo(index, event) {
Vue.set(this.todoList, index, event.target.value)
this.editTodo = null;
}
}
})
</script>
</body>
</html>

tolist2

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todoList</title>
<style>
#app {
margin:100px auto 0;
width:600px;
}
.input-box {
font-size:0;
}
.input-box input {
box-sizing: border-box;
width:500px;
font-size:16px;
border:1px solid #ccc;
padding:10px;
line-height: 18px;
}
.input-box button {
width:100px;
padding:10px;
font-size:16px;
border:1px solid #ccc;
background: #f5f5f5;
cursor: pointer;
line-height: 18px;
border-left:none;
}
ul {
list-style: none;
margin:0;
padding:0;
}
.todo-list {
margin-top:20px;
}
.todo-list li{
border:1px solid #ccc;
padding:10px;
margin-bottom:10px;
font-size:0;

}
.todo-list i {
margin-right:20px;
display: inline-block;
width:16px;
height:16px;
border:1px solid #ccc;
cursor: pointer;
vertical-align: -5px;
}
.todo-list p {
width:500px;
display: inline-block;
font-size:16px;
margin:0;
line-height: 20px;
}
.todo-list span {
display:inline-block;
height:20px;
line-height: 20px;
width:40px;
cursor: pointer;
color:red;
font-size:16px;

}
.done-list {
margin-top:20px;
}
.done-list li{
border:1px solid #ccc;
padding:10px;
margin-bottom:10px;
background: #999;
color:#ccc;
cursor: not-allowed;
text-decoration: line-through;
}
.edit-input {
width:576px;
font-size:16px;
line-height: 20px;
border:1px solid #eee;
}
</style>
</head>
<body>
<div id="app">
<div class="input-box">
<input type="text" v-model.trim="newTodo" placeholder="请输入代办事项">
<button @click="addTodo">添 加</button>
</div>

<div class="todo-list">
<ul>
<li v-for="(todo,index) in todoList" :key="index">
<div v-show="todo.isEdit">
<input type="text" class="edit-input" v-model.lazy="todo.content" @blur="doneEditTodo(todo)" v-todo-focus="todo.isEdit">
</div>
<div v-show="!todo.isEdit">
<i @click="addDone(index)"></i>
<p @dblclick="editTodo(todo)">{{ todo.content }}</p>
<span @click="deleteTodo(index)">&times;</span>
</div>
</li>
</ul>
</div>

<h3>已完成</h3>
<div class="done-list">
<ul>
<li v-for="done in doneList" :key="done">{{ done }}</li>
</ul>
</div>
</div>


<script src="../dist/js/vue.js"></script>
<script>
new Vue({
el:"#app",
data: {
todoList: [
{content:'今天代码敲三遍', isEdit: false},
{content: '晚上和小丽丽去钓鱼', isEdit: false}
],
doneList: [],
newTodo:'',
},
methods: {
addTodo() {
//如果输入框是空的,不执行
if (this.newTodo.length === 0) {
return;
}
//添加内容到 代办事项
this.todoList.push({content:this.newTodo, isEdit:false})
//清空输入框
this.newTodo = '';
},
deleteTodo(index) {
this.todoList.splice(index, 1)
},
addDone(index) {
//把内容添加到 doneList
this.doneList.push(this.todoList[index].content)
//从todoList删掉
this.deleteTodo(index);
},
editTodo(todo){
todo.isEdit = true;
},
doneEditTodo(todo) {
todo.isEdit = false;
}
},
directives: {
'todo-focus': function(el, binding) {
if (binding.value) {
el.focus(); //获取焦点
}
}
}
})
</script>
</body>
</html>

轮播图

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>轮播图</title>
<style>
.play {
margin:0px auto 0px;
width:1226px;
height:460px;
overflow: hidden;
position: relative;
}

.play img {
display: block;
width:1226px;
height:460px;
}
.img-wrapper a {
position: absolute;
left:0;
top:0;
width:1226px;
height:460px;
opacity: 0;
transition: opacity .5s;
}

.img-wrapper a.active {
opacity: 1;
}

.icon-wrapper{
position: absolute;
z-index: 100;
bottom:10px;
width:100%;
text-align: center;
font-size: 0;
cursor: pointer;
}

.icon-wrapper span {
display: inline-block;
width:10px;
height:10px;
border:1px solid #fff;
border-radius:6px;
margin:0px 5px;
transition: opacity .5s;
}
.icon-wrapper span.active {
background: rgba(255,255,255,.6)
}
.slider {
position: absolute;
z-index: 2000;
width:30px;
height:100px;
background: rgba(0,0,0,.5);
text-align: center;
line-height: 100px;
top:50%;
transform: translate(0,-50%);
color:#fff;
cursor: pointer;
opacity: 0;
transition: .5s;
}
.left{
left:0;
}
.right{
right:0;
}
.play:hover .slider {
opacity: 1;
}
</style>
</head>
<body>
<div id="app">

<div class="play" @mouseenter="onEnterFn" @mouseleave="onLeaveFn">
<div class="img-wrapper">
<a href="#" v-for="img,index in imgList" :class="{active:isActive(index)}">
<img :src="img">
</a>
</div>
<div class="icon-wrapper">
<span v-for="(img, index) in imgList" :class="{active:isActive(index)}" @mouseenter="controlImg(index)">{{index+1}}</span>
</div>

<div class="slider-wrapper">
<span class="slider left" @click="prev()"> < </span>
<span class="slider right" @click="next()"> > </span>
</div>
</div>
</div>

<script src="../dist/js/vue.js"></script>
<script>
let app = new Vue({
el:'#app',
data: {
imgList: ['../dist/images/play01.jpg','../dist/images/play02.jpg','../dist/images/play03.jpg','../dist/images/play04.jpg','../dist/images/play05.jpg'],
activeImg:0, //当前要显示的图片
delay:5000, //轮播时间间隔
timer: null, //定时器的返回值
},
methods: {
//判断是否是激活状态
isActive(index){
return this.activeImg === index;
},
//定时函数
runPlay(){
this.activeImg ++;
if (this.activeImg >= this.imgList.length) {
this.activeImg = 0;
}
},
//鼠标悬停到轮播图
onEnterFn(){
//停止定时
clearInterval(this.timer)
},
//鼠标离开轮播图
onLeaveFn(){
//重新定时
this.timer = setInterval(this.runPlay, this.delay)
},
//鼠标悬停在 控制按钮上
controlImg(index){
this.activeImg = index;
},
//上一个
prev(){
this.activeImg --;
if (this.activeImg < 0) {
this.activeImg = this.imgList.length - 1
}
},
//下一个
next() {
this.activeImg ++;
if (this.activeImg >= this.imgList.length) {
this.activeImg = 0;
}
}
},

//钩子 vue实例挂载到元素上
mounted(){
this.timer = setInterval(this.runPlay, this.delay)
}
});
</script>
</body>
</html>

基本使用

在子组件中声明 prop,然后添加一个 message

1
2
3
4
5
6
7
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})

一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。我们能够在组件实例中访问这个值,

然后直接传入值就可以在子组件中使用 message。

1
<child message="hello!"></child>

####

Prop 的大小写

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

传入一个对象的所有属性

1
<blog-post v-bind="post"></blog-post>

等价于

1
2
3
4
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>

Prop验证

我们可以为组件的 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
30
31
32
33
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 匹配任何类型)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组且一定会从一个工厂函数返回默认值
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})

类型列表:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
  • 自定义的构造函数

2.2 通过事件向父级组件发送消息

on(eventName)+on(eventName)+emit(eventName) 实现通讯

在父组件中使用 on(eventName)监听事件,然后在子组件中使用on(eventName)监听事件,然后在子组件中使用emit(eventName) 触发事件,这样就能实现子组件向父组件传值。

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
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})

new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
console.log('第'+this.total+'次点击')
}
}
})
1
2
3
4
5
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>

使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是非常有用的。这时可以使用 $emit 的第二个参数来提供这个值

1
2
3
4
incrementCounter: function () {
this.counter += 1
this.$emit('increment', this.counter)
}

然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值

1
<button-counter v-on:increment="postFontSize + $event"></button-counter>

或者,如果这个事件处理函数是一个方法:那么这个值将会作为第一个参数传入这个方法:

1
2
3
4
5
6
7
<button-counter v-on:increment="incrementTotal"></button-counter>

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

在组件上使用 v-model

组件内input需要满足条件:

  • 将其 value 特性绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
1
2
3
4
5
6
7
8
9
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})

v-model 在组件上的使用

1
2
3
4
5
6
7
<custom-input v-model="searchText"></custom-input>

<!-- 上面的写法 等价于 下面的写法 -->
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>

3 插槽 slot

1554201902306

3.1 通过插槽分发内容

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

·Something bad happened.· 会替换掉 slot标签

3.2 模板中多个插槽

组件模板

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

调用组件

1
2
3
4
5
6
7
8
9
10
<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<p slot="footer">Here's some contact info</p>
</base-layout>

3.3 插槽默认内容

1
2
3
<button type="submit">
<slot>Submit</slot>
</button>

4. 动态组件

1554203631022

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动态组件</title>
<link rel="stylesheet" href="../dist/css/bootstrap.css">
<style>
.panel {
border-top: none;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="page-header">
<h1>动态组件</h1>
</div>
<div class="row">
<div class="col-md-6">
<ul class="nav nav-tabs">
<li v-for="item,index in tabs" :class="{active:isTab(index)}" @click="setTab(index)"><a href="javascript:0">{{ item.tabName }}</a></li>
</ul>

<div class="panel">
<div class="panel-body">
<keep-alive>
<component :is="tabs[tab].tabComponent"></component>
</keep-alive>
</div>
</div>
</div>
</div>
</div>
</div>

<script src="../dist/js/vue.js"></script>
<script>
//创建根实例
new Vue({
el:'#app',
data: {
tabs: [
{'tabName':'登录', 'tabComponent':'login-component'},
{'tabName':'注册', 'tabComponent':'register-component'},
{'tabName':'免登录', 'tabComponent':'no-login-component'},
],
tab: 0
},
methods: {
isTab(index) {
return this.tab === index;
},
setTab(index) {
this.tab = index;
}
},
components: {
'login-component': {
template: `
<form action="#">
<div class="form-group">
<label for="#">用户名</label>
<input type="text" class="form-control" />
</div>
<div class="form-group">
<label for="#">密码</label>
<input type="password" class="form-control" />
</div>
<button class="btn btn-default btn-block">登录</button>
</form>
`
},
'register-component': {
template: `
<form action="#">
<div class="form-group">
<label for="#">用户名</label>
<input type="text" class="form-control" />
</div>
<div class="form-group">
<label for="#">密码</label>
<input type="password" class="form-control" />
</div>
<div class="form-group">
<label for="#">确认密码</label>
<input type="password" class="form-control" />
</div>
<button class="btn btn-primary btn-block">注册</button>
</form>
`
},
'no-login-component': {
template:`
<div>
我是李刚,我不用登录,我牛逼
</div>
`
}
}
})
</script>
</body>
</html>

4.1 实现动态组件

在不同组件之间进行动态切换

1
<component is="组件名" class="tab"></component>

实现选项卡案例

4.2 在动态组件上使用 keep-alive

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

主要用于保留组件状态或避免重新渲染

1
2
3
4
5
6
7
8
9
10
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>

4.3 绑定组件选项对象

动态组件可以绑定 组件选项对象(有component属性的对象),而不是已注册组件名的示例

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
var tabs = [
{
name: 'Home',
component: {
template: '<div>Home component</div>'
}
},
{
name: 'Posts',
component: {
template: '<div>Posts component</div>'
}
},
{
name: 'Archive',
component: {
template: '<div>Archive component</div>',
}
}
]

new Vue({
el: '#dynamic-component-demo',
data: {
tabs: tabs,
currentTab: tabs[0]
}
})
1
2
3
4
5
<component
v-bind:is="currentTab.component"
class="tab"
>
</component>

5 组件的其他特性

5.1 解析 DOM 模板时的注意事项

给组件 指定 class和style 会继承到 组件模板的根元素上

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

1
2
3
<table>
<blog-post-row></blog-post-row>
</table>

上面的写法,渲染效果会不甚理想,可以采用以下写法

1
2
3
<table>
<tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:

  • 字符串 (例如:template: ‘…’)
  • 单文件组件 (.vue)
  • <script type="text/x-template">

5.2 Prop的一些问题

Prop的属性名问题

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

如果你使用字符串模板,那么这个限制就不存在了。

非Prop属性

组件上定义的非Prop属性 会传递到 组件模板的根元素上

class 和 style 特性会非常智能,即两边的值会被合并起来

对prop重新赋值

子组件中,对prop重新赋值,会报警告

5.3 组件事件的相关问题

将原生事件绑定到组件

想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符

1
<base-input v-on:focus.native="onFocus"></base-input>

.sync 修饰符

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”

推荐以 update:my-prop-name 的模式触发事件

1
2
//子组件中
this.$emit('update:title', newTitle)
1
2
3
4
5
<!-- 上级组件 模板中 -->
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>

以上写法可以换成下列写法

1
<text-document v-bind:title.sync="doc.title"></text-document>

5.4 官方文档-组件

深入了解组件

网址: https://cn.vuejs.org/v2/guide/components-registration.html

注意事项

实现prop的双向数据绑定

1554210092166

1
2
3
4
5
6
7
8
9
10
<table>
<tr is="组件名"></tr>
</table>

给组件标签 设置的classstyle 会自动添加到 组件模板的根元素上

在组件标签上添加原生事件 <my-component @click.native="">

实现prop的双向数据绑定 :属性名.sync
子组件要配合, $this.$emit('update:属性名', 新值)
图灵python大海老师 wechat
python分享公众号
坚持原创技术分享,您的支持将鼓励我继续创作!