vue组件间通信

一、Props和emit

父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
1.父组件向子组件传值
接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据 users:[“Henry”,”Bucky”,”Emily”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

//App.vue父组件
<template>
<div id="app">
<users v-bind:users="users"></users>//前者自定义名称便于子组件调用,后者要传递数据名
</div>
</template>
<script>
import Users from "./components/Users"
export default {
name: 'App',
data(){
return{
users:["Henry","Bucky","Emily"]
}
},
components:{
"users":Users
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//users子组件
<template>
<div class="hello">
<ul>
<li v-for="user in users">{{user}}</li>//遍历传递过来的值,然后呈现到页面
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props:{
users:{ //这个就是父组件中子标签自定义名字
type:Array,
required:true
}
}
}
</script>

总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed

二、EventBus

eventBus可以在全局定义,实现全项目通讯,使用方法也很简单。

1、初始化——全局定义
全局定义,可以将eventBus绑定到vue实例的原型上,也可以直接绑定到window对象上.

1
2
3
4
5
//main.js
//方式一
Vue.prototype.$EventBus = new Vue();
//方式二
window.EventBus = new Vue();

2、触发事件

1
2
3
4
//使用方式一定义时
this.$EventBus.$emit('eventName', param1,param2,...)
//使用方式二定义时
EventBus.$emit('eventName', param1,param2,...)

3、监听事件

1
2
3
4
5
6
7
8
//使用方式一定义时
this.$EventBus.$on('eventName', (param1,param2,...)=>{
//需要执行的代码
})
//使用方式二定义时
EventBus.$on('eventName', (param1,param2,...)=>{
//需要执行的代码
})

4、移除监听事件

为了避免在监听时,事件被反复触发,通常需要在页面销毁时移除事件监听。或者在开发过程中,由于热更新,事件可能会被多次绑定监听,这时也需要移除事件监听。

1
2
3
4
//使用方式一定义时
this.$EventBus.$off('eventName');
//使用方式二定义时
EventBus.$off('eventName');

三、Provide和Inject

官方解释
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的property。
inject
可以是一个字符串数组、也可以是一个对象
说白了,就是provide在祖先组件中注入,inject 在需要使用的地方引入即可。
我们可以把依赖注入看做一部分大范围的prop,只不过它以下特点:
● 祖先组件不需要知道哪些后代组件使用它提供的属性
● 后代组件不需要知道被注入的属性是来自那里

注意:
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

下面举一个例子:

祖先
index.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
57
58
59
60
61
62
63
64
65
66
67
<template>
<div class="grandPa">
爷爷级别 : <strong>{{ nameObj.name }} 今年 <i class="blue">{{ age }}</i>岁, 城市<i class="yellow">{{ city }}</i></strong>
<child />
<br>
<br>
<el-button type="primary" plain @click="changeName">改变名称</el-button>

</div>
</template>

<script>
import child from '@/components/ProvideText/parent'
export default {
name: 'ProvideGrandPa',
components: { child },
data: function() {
return {
nameObj: {
name: '小布'
},
age: 12,
city: '北京'
}
},
provide() {
return {
nameObj: this.nameObj, //传入一个可监听的对象
cityFn: () => this.city, //通过computed来计算注入的值
age: this.age //直接传值

}
},
methods: {
changeName() {
if (this.nameObj.name === '小布') {
this.nameObj.name = '貂蝉'
this.city = '香港'
this.age = 24
} else {
this.nameObj.name = '小布'
this.city = '北京'
this.age = 12
}
}
}
}
</script>

<style lang="scss" scoped>
.grandPa{
width: 600px;
height:100px;
line-height: 100px;
border: 2px solid #7fffd4;
padding:0 10px;
text-align: center;
margin:50px auto;
strong{
font-size: 20px;
text-decoration: underline;;
}
.blue{
color: blue;
}
}
</style>

中间组件
parent.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
<template>
<div class="parent">
父亲级别 : <strong>只用作中转</strong>
<son />
</div>
</template>

<script>
import Son from './son'
export default {
name: 'ProvideParent',
components: { Son }
}
</script>

<style lang="scss" scoped>
.parent{
height:100px;
line-height: 100px;
border: 2px solid #feafef;
padding:0 10px;
margin-top: 20px;
strong{
font-size: 20px;
text-decoration: underline;;
}

}
</style>

后代组件
son.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
<template>
<div class="son">
孙子级别 : <strong>{{ nameObj.name }} 今年 <i class="blue">{{ age }}</i>岁, 城市<i class="yellow">{{ city }}</i></strong>
</div>
</template>

<script>
export default {
name: 'ProvideSon',
//inject 来获取的值
inject: ['nameObj', 'age', 'cityFn'],
computed: {
city() {
return this.cityFn()
}
}
}
</script>

<style lang="scss" scoped>
.son{
height:100px;
line-height: 100px;
padding:0 10px;
margin: 20px;
border: 1px solid #49e2af;
strong{
font-size: 20px;
text-decoration: underline;;
}
.blue{
color: blue;
}
}
</style>

执行之后会发现一个小细节。
无论我点击多少次,孙子组件的年龄age字段永远都是12并不会发生变化。
正是官网所提到的provide 和 inject绑定并不是可响应的。这是刻意为之的。
所以大家使用的时候,一定要注意注入的方式,不然很可能无法实现数据响应。

四、vuex

vuex的通信,大家可以前往我的另一篇博客vue状态管理之Vuex

五、$attrs/$listeners

1.简介
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法—-$attrs/$listeners
● $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。通常配合 inheritAttrs 选项一起使用。
● $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件
接下来我们看个跨级通信的例子:

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

// index.vue
<template>
<div>
<h2>浪里行舟</h2>
<child-com1
:foo="foo"
:boo="boo"
:coo="coo"
:doo="doo"
title="前端工匠"
></child-com1>
</div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
components: { childCom1 },
data() {
return {
foo: "Javascript",
boo: "Html",
coo: "CSS",
doo: "Vue"
};
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// childCom1.vue
<template class="border">
<div>
<p>foo: {{ foo }}</p>
<p>childCom1的$attrs: {{ $attrs }}</p>
<child-com2 v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
components: {
childCom2
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
props: {
foo: String // foo作为props属性绑定
},
created() {
console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// childCom2.vue
<template>
<div class="border">
<p>boo: {{ boo }}</p>
<p>childCom2: {{ $attrs }}</p>
<child-com3 v-bind="$attrs"></child-com3>
</div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
components: {
childCom3
},
inheritAttrs: false,
props: {
boo: String
},
created() {
console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// childCom3.vue
<template>
<div class="border">
<p>childCom3: {{ $attrs }}</p>
</div>
</template>
<script>
export default {
props: {
coo: String,
title: String
}
};
</script>

没有继承数据的对象,格式为{属性名:属性值}。Vue2.4提供了$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。
简单来说:$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。

六、$parent / $children与 ref

● ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
● $parent / $children:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// component-a 子组件
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 父组件
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>