vue 组件开发之组件通信

最近的学习工作中都用到了饿了么的 element-ui, 以及移动端的 Mint-ui, 借此机会,想深入学习 Vue 的组件开发流程及原理,故写此篇博客,记录所学及所感.

本例程以一个 table 组件为例,至于为什么想做这个 table 组件,是看到 element-ui 中有这么一个强大的 table 组件,感到很好奇。本篇讲解组件间通信 (父子 / 非父子), 以及运用 slot 的使用方法 (下篇).

基本构造

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
// in UlTest.vue 组件测试用例
<template>
<t-table :items="items" border center>
<t-table-item
label="操作">
<template scope="prop">
<el-button type="primary" @click="showData(prop.index, prop.rowData)">删除{{prop.text}}</el-button>
</template>
</t-table-item>
<t-table-item
label="数字"
width="120px"
prop="text"
></t-table-item>
<t-table-item
label="年龄"
width="150px"
prop="age"
></t-table-item>
<t-table-item
label="名字"
prop="name"
></t-table-item>
</t-table>
</template>

<script>
export default {
data(){
return {
items: [
{
text: '123',
name: 'tzx',
age: 13
},
{
text: '456',
name: 'hanya',
age:12
}
]
}
},
methods: {
click(){
alert('click')
},
showData(index, data){
console.log(index, data);
}
},
components: {
// TTable, TTableItem组件已在全局注册!!!
},
mounted(){
console.log('UlTest is mounted');
}
}
</script>

所有代码可在这里找到.

组件渲染顺序

在 Vue 中,组件的渲染顺序是由里向外的。在本例中,会先渲染 TTableItem, 其次是 TTable (如果里面有其他组件,会先渲染其他组件), 最后是 UlTest 组件.
下图是 Vue 组件的生命周期图示
生命周期
由图可知,在 created 事件完成时,数据绑定和事件初始化完成,当允许编译模板时 (有 el 选项或者有 template 提供), 将模板编译成 render 函数,因此,当此时 render 函数中有其他组件时,会进入此组件的生命周期。所以,created 钩子是从外组件到内组件的,即外组件的 created 调用后,才可能进入内部其他组件的生命周期钩子函数如 created.

1
2
3
4
5
6
7
8
9
10
UlTest is created
TTable is created
TTableItem is created
TThead is created.
TTbody is created
4 TTableItem is mounted
TThead is mounted
TTbody is mounted111111
TTable is mounted
UlTest is mounted

对于 mounted 函数,在同一组件层面的组件,如 TTable 中的 TThead 和 TTbody, 先 created 的组件会先 mounted, 好理解吧??
理解清楚这些周期函数有助于理解组件间通信的问题.

父子组件通信

先说说简单的,父子组件通信.

父对子通信

在最上面的 UlTest.vue 组件中,template 元素下直接包含一个 t-table 组件,可以理解为 t-table 是 UlTest 的子组件 (不晓得这么理解对不对). 对 t-table 组件,我们传递了 3 个参数,一个是绑定的 items 参数,两个是布尔值参数 (border center). 在 TTable.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
54
55
56
<script>
import TTbody from './TTbody'
import TThead from './TThead'
export default {
props: {
items: {
type: Array,
default: ()=>[]
},
border: {
type: Boolean,
},
center: {
type: Boolean
}
},
tableId: 1,
components: {
TTbody,
TThead,
},
data(){
return {
dataTable: this.items,
tableHeader: [],
scopedSlot: null,
}
},
computed: {
tableRow(){
return this.items;
},
_bus(){
return this.$refs.bus;
}
},
created(){
this.$on('init',this.handle)
this.$on('initScope',this.handleScope)
console.log('TTable is created');
},
methods:{
handle(d){
this.tableHeader.push(d);
},
handleScope(d){
if (d) {
this.scopedSlot = d;
}
}
},
mounted(){
console.log('TTable is mounted');
}
}
</script>

即在子组件中,通过定义 props 对象,即可完成子组件接受父组件的数据.
props 可以简单地定义为字符串数组 (不能验证数据类型), 也可以定义为对象,每个键为接收的参数名,值对参数对象,可设置 type, required, default (数组 / 对象等引用类型的默认值需要使用函数返回数据),validator 等.

以上为父对子的通信,下面看子对父的通信方式.

子对父的通信

这里用一个简单的例子说明:

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
<div id="app">
<p>
{{num}}
</p>
<t-t1 @change="changeNum">
</t-t1>
<t-t2>
</t-t2>
</div>


var bus = new Vue();//简易事件触发/捕获器
var TT1 = {
data() {
return {
num: 1,
};
},
methods: {
handle(d){
this.num = d;
},
changeFa(){
this.$emit('change',666);
}
},
created(){
bus.$on('change', this.handle)
},
template: `<div><p>{{this.num}}</p><button @click="changeFa">发送1</button></div>`,
};
var TT2 = {
data() {
return {
num: 1,
};
},
methods: {
send(){
bus.$emit('change', 4);
}
},
template: `<div><p>{{this.num}}</p><button @click='send'>发送2</button></div>`,
}
var Main = {
data() {
return {
num:1,
};
},
methods: {
changeNum(d){
this.num = d;
}
},
components: {
TT1,
TT2,

}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

本例中,包含了非父子通信的简单实现 (分析见下节).

主要说子对父通信。在 tt1 组件中,注册 change 事件,绑定处理函数到父组件的 changeNum 函数上,当子组件通过 vm.$emit (‘change’, props) 触发时,会被父组件捕获并交由 changeNum 函数处理。即完成了子对父的通信.

非父子通信

如上一节中代码所示,可以 let bus = new Vue (); 用 bus 作为简单的中间处理系统。通过在不同组件中用 bus.$on/.$emit 注册 / 发送事件,实现任意组件间的通信.


需要注意的,在组件上注册事件时,尽量用一个单词或者是中划线格式的,如不能写 @changeNumber=”changeNum”, 可以改为 @change=”changeNum” 或者是 change-number=”changeNum”, 并且,$emit 中事件名分别对应’change’或者’change-number’

本篇完结,大家鼓掌!!!明天更新关于 slot/scopedSlot 的相关实践.