“组件化、模块化、工程化”是现在前端中不可回避的话题,在Vue开发中体现的尤为明显,组件与组件之间的通信也有多种类型,参考下图:

组件间通信

一、父组件向子组件通信

1.1.props传递数据

如上图中父组件向子组件通信使用props传递数据,在组件中使用props选项来声明需要从父级接收的数据,props值有两种:一种是字符串数组,一种是对象。

1
2
3
4
<div id="root">
<input type="text" v-model="parentMessge" />
<my-component :parent-data="parentMessage"></my-component>
</div>
1
2
3
4
5
6
7
8
9
10
Vue.component('my-component', {
props: ['parentData'],
template: '<div>来自父组件的数据:{{parentData}}</div>'
})
new Vue({
el: '#root',
data: {
parentMessage: ''
}
})

父组件my-component绑定了一个数据parentData,数据来源于input的输入值,组件内的template里包含一个div,并显示相应的数据。

1.2.单向数据流

Vue2.x和Vue1.x比较大的一个改变是Vue2.x通过props传递的数据是单项流的,也就是说父级props的更新会向下流到子组件中,但是反过来不行,这样尽可能的解耦父子组件,避免子组件意外修改父组件的状态,从而
导致你的应用的数据流难以理解,详见官文单项数据流

1
2
3
4
<div id="app">
<p>父组件数据:{{pData}}</p>
<single-component :init-count="pData"></single-component>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.component('single-component', {
props: ['initCount'],
template: '<div>{{count}}<button @click="handleClick">+1</button></div>',
data: function(){
return {
count: this.initCount
}
},
methods: {
handleClick: function(){
this.count++
}
}
});
new Vue({
el: '#app1',
data: {
pData: 3
}
})

子组件中声明一个本地data数据count来保存父组件传递过来的数据,之后就与父组件的数据无关,不管count如何修改,都不会影响父组件的数据,当我们点击+1按钮对count执行+1操作,此时父组件数据并不受影响。

二、子组件向父组件通信

2.1.自定义事件

当子组件要向父组件传递数据时,可以用到v-on进行自定义事件,子组件通过$emit()来触发事件,父组件通过$on()来监听事件。

1
2
3
4
5
6
7
<div id="root">
<p>总数: {{ total }}</p>
<my-component
@increase="handleGetTotal"
@reduce="handleGetTotal"
></my-component>
</div>
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', {
template: '<div>' +
'<button @click="handleIncrease">+1</button>' +
'<button @click="handleReduce">-1</button>' +
'</div>',
data: function(){
return {
counter: 0
}
},
methods: {
handleIncrease: function(){
this.counter++;
this.$emit('increase', this.counter);
},
handleReduce: function(){
this.counter--;
this.$emit('reduce', this.counter);
}
}
});

new Vue({
el: '#root',
data: {
total: 0
},
methods: {
handleGetTotal: function(total){
this.total = total;
}
}
})

这里在组件的template的两个按钮自定义handleIncreasehandleReduce事件执行对组件数据的增减,然后通过this.$emit触发increasereduce事件并传递组件中的数据,在父组件中通过@increase@reduce监听事件,并执行相应的操作。

2.2.自定义组件使用v-model

Vue2.x可以在自定义组件上使用v-model,实际上是@input事件的一个语法糖。

1
2
3
4
5
6
<div id="app">
<p>总数:{{total}}</p>
<model-component v-model="total"></model-component>
<p>总数:{{total1}}</p>
<model-component @input="handleGetTotal"></model-component>
</div>
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('model-component',{
template: '<button @click="handleClick">+1</button>',
data: function(){
return {
counter: 0
}
},
methods: {
handleClick: function(){
this.counter++;
this.$emit('input', this.counter);
}
}
});

new Vue({
el: '#app',
data: {
total: 0,
total1: 0
},
methods: {
handleGetTotal: function(total){
this.total1 = total;
}
}
});

这里自定义了一个组件model-component,引用了两次,第一次使用v-model指令绑定total数据,第二次使用@input事件绑定handleGetTotal方法;而在组边template的按钮里使用@click绑定handleClick,在方法里面对数据执行操作并触发input事件,传递了处理后的数据。

三、非父子组件间的通信

3.1.借助中央事件总线bus

中央处理总线就是一个全局的空的vue实例,用于承载组件间通信的操作,从单词意思也可以看出。

1
2
3
4
5
6
<div id="app">
<p>实例下显示孙组件的内容:{{message}}</p>
<wrap-component>
<in-component></in-component>
</wrap-component>
</div>
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
var bus = new Vue();

var Child = {
template: '<div><h6>这是内层孙组件</h6><button @click="handleEvent">孙按钮派发事件</button></div>',
methods: {
handleEvent: function(){
bus.$emit('on-message', '来自孙组件的内容');
}
}
}

Vue.component('wrap-component',{
template: '<div><slot></slot>这是外层组件</div>',
components: {
'in-component': Child
}
});

new Vue({
el: '#app',
data: {
message: ''
},
components: {
'in-component': Child
},
mounted: function(){
var _this = this;
bus.$on('on-message', function(msg){
_this.message = msg;
})
}
})

这里首先创建了一个名为bus的Vue空实例,用js对象的形式创建了一个组件,并在wrap-component组件中注册,由于局部注册的组件在其子组件中不可用,所以在根实例中再注册一次。局部组件通过按钮事件向bus触发一个’on-message’事件,传递相应的数据,并在app vue实例下监听相应的事件。

3.2.父链和子链

在子组件中,可以通过this.$parent访问该组件的父实例和组件,同样的可以通过this.$children访问它的子组件,可以无限上下递归,直到最内层的组件或根实例。

1
2
3
4
<div id="app">
<p>通过父链修改的数据:{{message}}</p>
<component-a></component-a>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vue.component('component-a',{
template: '<button @click="handleEvent">通过父链直接修改数据</button>',
methods: {
handleEvent: function(){
this.$parent.message = '来自组件component-a的内容';
}
}
});

new Vue({
el: '#app',
data: {
message: '默认数据,点击按钮被修改'
}
});

这里直接通过组件的按钮修改了父实例的数据。

3.3.子组件索引

当子组件过多时,多次使用$children显然不可取,这时候可以通过ref属性给组件一个索引名称。

1
2
3
4
<div id="app">
<button @click="handleRef">通过ref获取子组件实例</button>
<component-b ref="comA"></component-b>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vue.component('component-b', {
template: '<div>子组件</div>',
data: function(){
return {
message: '子组件内容'
}
}
});

new Vue({
el: '#app',
methods: {
handleRef: function(){
//通过$refs来访问指定的实例
var msg = this.$refs.comA.message;
console.log(msg);
}
}
})

这里this.$refs.comA获取到指定索引的组件,注意的是,$refs只在组件渲染完成后才填充,并且它是非响应式的,应该避免在模板和计算属性中使用。