vue组件之间的数据传递

父子组件的通信

在vue组件关系中最常见的就是父子组件,组件A在它的模板中使用了组件B,他们之间的通信方式是:父组件(A)通过prop给子组件(B)下发数据,子组件(B)通过事件给父组件(A)发送消息。
'vue-props-events'
prop这样的数据流称之为单向数据流,即父组件内的属性变化时,子组件相应的属性也会同步更新,而子组件属性的变化不会影响父组件的属性。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

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
<div id="app">
<child :my-name="name" :my-age="age">
</div>
<template id="child">
<div class="child">
<h3>子组件child数据</h3>
<ul>
<li>
<label>姓名</label>
<span>{{ myName }}</span>
</li>
<li>
<label>年龄</label>
<span>{{ myAge }}</span>
</li>
</ul>
</div>
</template>
let parent = new Vue({
el: "#app",
data() {
return {
name:"jack",
age: 28
}
},
components: {
"child": {
template:"#child",
props:["myName", "myAge"]
}
}
})

结果
result
在很多组件的应用中往往也会涉及到prop得到修改:
1.Prop 作为初始值传入后,子组件想把它当作局部数据来用;
2.Prop 作为原始数据传入,由子组件处理成其它数据输出。
所以当你想要实现子组件的prop修改影响父组件(虽然不推荐使用),可以使用.sync修饰符(在vue 2.3.0版本重新引入)打到prop的双向绑定,它会作为编译时的语法糖,扩展为一个自动更新父组件属性的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
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
<div id="app">
<div class="parent">
<h3>父组件Parent数据</h3>
<ul>
<li>
<label>姓名:</label>
<span>{{ name }}</span>
<input type="text" v-model="name" />
</li>
<li>
<label>年龄:</label>
<span>{{ age }}</span>
<input type="text" v-model="age" />
</li>
</ul>
</div>
<child v-bind:my-name.sync="name" v-bind:my-age.sync="age"></child>
</div>
<template id="child">
<div class="child">
<h3>子组件child数据</h3>
<ul>
<li>
<label>姓名</label>
<span>{{ myName }}</span>
<input type="text" v-model="myName" />
</li>
<li>
<label>年龄</label>
<span>{{ myAge }}</span>
<input type="text" v-model="myAge" />
</li>
</ul>
</div>
</template>
let parent = new Vue({
el: '#app',
data () {
return {
name: 'w3cplus',
age: 7
}
},
components: {
'child': {
template: '#child',
props: ['myName', 'myAge']
}
}
})

另外一种父子组件通信的方式是使用Vue.$emit自定义事件。

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
<div id="app">
<div class="parent">
<h3>父组件Parent数据</h3>
<ul>
<li>
<label>姓名:</label>
<span>{{ name }}</span>
<input type="text" v-model="name" />
</li>
<li>
<label>年龄:</label>
<span>{{ age }}</span>
<input type="text" v-model="age" />
</li>
</ul>
</div>
<child v-bind:my-name="name" v-bind:my-age="age" @update:my-name='updateName' @update:my-age='updateAge'></child>
</div>
<template id="child">
<div class="child">
<h3>子组件child数据</h3>
<ul>
<li>
<label>姓名</label>
<span>{{ myName }}</span>
<input type="text" v-model="childMyName" />
</li>
<li>
<label>年龄</label>
<span>{{ myAge }}</span>
<input type="text" v-model="childMyAge" />
</li>
</ul>
</div>
</template>
let parent = new Vue({
el: '#app',
data () {
return {
name: 'jack',
age: 7
}
},
methods:{
updateName(val){
this.name = val;
},
updateAge(val){
this.age = val;
}
},
components: {
'child': {
template: '#child',
props: ['myName', 'myAge'],
data() {
return {
childMyName:this.myName,
childMyAge:this.myAge
}
},
watch:{
childMyName(val) {
this.$emit('update:my-name',val);
},
childMyAge(val) {
this.$emit('update:my-age',val);
}
}
}
}
})

非父子组件通信

有时候,非父子关系的两个组件也需要通信,一般也可以采用Vue.$emit事件实现。一般在项目中,组件都是分模块文件放置的,比如有两个组件componentA.js,componentB.js,两个文件需要通信时,我们一般会创建一个事件处理文件event.js。

1
2
3
import Vue from "vue";
let bus = new Vue();
export default bus;

触发组件 A 中的事件bus.$emit(‘id-selected’, “xxx”);
在组件 B 创建的钩子中监听事件
bus.$on(‘id-selected’, function (id) {
// …
})
当数据结构较为简单时,推荐使用这种方式;但项目中往往会碰到数据结构较为复杂的情况,这是可以使用状态管理模式vuex。在vuex store中的数据都是响应式的, 跟data数据里面的一样使用,一个组件变化了,另一个组件中的数据自然而然的跟着做响应了。

多层级父子组件通信

在Vue1.0中实现了$broadcast与$dispatch两个方法用来向子组件(或父组件)广播(或派发),当子组件(或父组件)上监听了事件并返回true的时候会向爷孙级组件继续广播(或派发)事件。但是这个方法在Vue2.0里面已经被移除了。在饿了么开源组件库elementUI中又重新实现了broadcast和dispatch.但是跟Vue1.0的两个实现方法略有不同。这两个方法实现了向子孙组件广播以及向多层级父组件事件派发的功能。但是并非广义上的时间广播,它需要指定一个commentName进行向指定组件名组件定向广播(派发事件)。这两个方法的实现是用到$parent和$children,用以遍历子节点或逐级向上查询父节点,访问到指定组件名的时候,调用$emit触发指定事件。

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
function broadcast(commentName, eventName, params) {
/*遍历当前节点下的所有子组件*/
this.$children.forEach(child => {
/*获取子组件名称*/
var name = child.$options.commentName;
if (name === commentName) {
/*如果是我们需要广播到的子组件的时候调用$emit触发所需事件,在子组件中用$on监听*/
this.$emit.apply(child, [eventName].concat(params));
} else {
/*非所需子组件则递归遍历深层次子组件*/
broadcast.apply(child, [commentName, eventName].concat(params));
}
})
}
export default {
methods: {
/*对多级父组件进行事件派发*/
dispatch(commentName, eventName, params) {
/*获取父组件,如果以及是根组件,则是$root*/
var parent = this.$parent || this.$root;
/*获取父节点的组件名*/
var name = parent.optins.commentName;
while(parent && (!name || name !== commentName)) {
/*当父组件不是所需组件时继续向上寻找*/
parent = parent.$parent;
if (parent) {
name = parent.optins.commentName;
}
}
/*找到所需组件后调用$emit触发当前事件*/
if(parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
/*
向所有子组件进行事件广播。
这里包了一层,为了修改broadcast的this对象为当前Vue实例
*/
broadcast(commentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
}