前言
Vue组件实例间的作用域是相互独立的,而通常一个页面是由很多个组件构成,这些组件可能又嵌套了组件,形成了一个关系网图,它们的关系可能是像下图中一样,大致分为两种使用场景,父子组件间通信和非父子组件间通信,父子组件间通信又分为直接父子关系和间接父子关系。vue提供了多种通信方法,针对不同的通信需求,选择最合适的通信方式能帮助我们提高开发效率。本文简要介绍了八种通信方式以及适用场景,记录下在学习中的收货。
父子组件间的通信
props和$emit
这是父子组件在传参时中常用的一种方式,父组件通过v-bind传入参数,子组件通过props来接收,子组件通过内建的$emit方法传入事件名来触发一个事件,父组件通过v-on像监听原生DOM事件一样来监听这个事件。
// 父组件CompFather
父组件的title_name:{{ title_name }}
// 子组件CompSon
子组件的titleName:{{titleName}}
适用场景:适用于直接父子关系(中间无嵌套组件)的组件间进行通信。
$attrs
和$listeners
$attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件。
// Comp1.vue
// Comp2.vue
comp2的$attrs:{{$attrs}}
// Comp3.vue
comp3的$attrs:{{ $attrs }}
这里Comp2的props接收了Comp1传入的flag,因此Comp2中的$attrs
没有flag参数,Comp3接收了msg,因此$attrs
只有id。
适用场景:组件之间跨级传参,可以使用$attrs
属性,这样使得代码更加简洁,更利于维护。
$listeners
包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件。
当我们要封装一个input类型的组件时,我们可以通过.native
的方式监听它的原生事件。
// App.vue
// MyInput.vue
但如果此时的input
被label
标签包裹了一层,input
不再是根组件时,父组件的.native将监听失效,于是vue给我们提供了$listeners
属性来解决这个问题。
// MyInput.vue
以下是使用$listeners
属性进行监听:
// App.vue
适用场景:当需要监听子组件的原生事件,且子组件可能是经过重构的组件时,可以使用$listeners
来代替.native
监听,当然也不只是包含原生事件监听器,只是在这里监听原生事件比较合适,我们还有其它方式来做事件监听。
$children
/$parent
/$root
$children
是当前实例的直接子组件,不保证顺序,数组顺序不一定是子组件在该父组件中的渲染顺序,也不是响应式的。
如果当前实例有父组件的话,$parent
则是当前实例的父实例。
$root
是当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。通过this.$root
来访问。
缺点:parent.parent...的情况,对于后续维护不友好。
适用场景:尽量使用其它方式来代替$parent/$chilren
ref
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
// App.vue
适用场景:当使用element-ui组件时,可用于调用组件方法,例如el-table
组件的选择表格项,排序等等。
v-model
一个组件上的 v-model
默认会利用名为 value
的 prop 和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突。
// App.vue
适用场景:v-model
适用于在封装input
类型的组件时,用于给数据进行双向绑定,如果不使用v-model
,则需要在父组件增加一个方法来监听事件然后改变父组件中的值,显得非常麻烦且不简洁。
Sync修饰符
sync
与v-model
非常类似,也是适用于需要子组件改变父组件的值时。 :isShow.sync="isShow"其实是 @update:isShow="bol=>isShow=bol"语法糖
我是一个子组件,我在红色的海洋里!
provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
provide
在父组件中将子组件要使用的数据抛出,然后在子组件中inject
注入要使用的数据,它不像使用props需要层层传递,中间不需要这个数据的地方也需要声明,provide
和inject
只注重“源头”和“终点”,主要用于在高阶组件库中使用,在平常的开发中一般不适用的原因是不方便追溯“源头”,不知道是哪一层声明的和使用了。
子孙层中的provide
会覆盖祖父层中相同key的属性值。
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。
inject
选项应该是一个字符串数组或一个对象,from
是选自祖父组件provide
的哪个值的key,default
指定一个默认值。
// 父组件
provide: ["message"],
// 子组件
inject: {
msg: {
from: "message",
default: "default value"
}
}
// 父组件CompFather
// 子组件 CompSon
子组件
provide
和 inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。例如provide
和inject
的是一个对象时,该数据是可响应的。
// 当provide/inject的是一个对象时,是可响应的,子组件改变值,父组件也会改变,父组件改变子组件也会改变
provide() {
return {
msg: this.msg
}
},
data() {
return {
msg: {
title: "父组件msg"
}
}
}
适用场景:适用于封装高阶组件,祖先实例不关心哪个后代实例会用到,后代实例不关心数据来源于哪个祖先实例。
非父子组件间的通信
中央事件总线 bus
使用中央事件总线实际就是创建一个vue实例,利用这个vue实例来传递消息。
使用方式一:
// 定义一个bus文件
import Vue from "vue";
const bus = new Vue();
export default bus;
// 引入bus文件
import bus from "@/bus";
bus.$emit("myEvent", "bus msg")
// 引入bus文件
import bus from "@/bus";
bus.$on("myEvent", data => {
console.log(data);
});
使用方式二:
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.prototype.$bus = new Vue();
new Vue({
render: h => h(App),
}).$mount('#app')
// 发送事件
this.$bus.$emit("myEvent", "bus msg");
// 接收事件
this.$bus.$on("myEvent", data => {
console.log(data);
})
适用场景:适用于不是特别大的单页面应用跨级跨兄弟组件间通信。
VueX
当我们在开发大型但单页应用时,组件间需要频繁使用修改某些值,这时候使用组件间传参就显得非常繁杂混乱,且不利于管理维护,vue为解决这一问题提供了VueX
这个状态管理工具,我们只需要将多个组件需要共享的状态放VueX
中进行统一管理,形成一个全局单例管理模式,即可实现组件数据同步。
VueX
的将要统一管理的状态放在state中,这样在引用了VueX
的页面中就能获取到状态,actions
主要是处理例如后台请求的一些异步操作,保证数据的同步,通过dispatch
方法调用actions
中的方法,当得到异步的数据之后再通过commit
调用mutations
中的方法改变state
。不要在组件中通过$store.state直接修改state数据,这样Devtools
是监控不到的,要经过mutations
才可以。
这里只是简要介绍通信方式,详细的使用方法参见
[VueX
官网] https://vuex.vuejs.org/zh/
总结
通信方式 | 适用场景 |
---|---|
props /$emit |
直接父子组件传值 |
$attrs /$listeners |
$attrs 非直接父子组件间传值,$listeners 监听原生事件 |
$children /$parent |
一般用其它方式代替,不方便维护 |
ref |
可用于调用高阶组件方法,如注册element-ui 组件引用 |
v-model |
封装需要双向绑定的组件时用v-modal 进行传参 |
sync 修饰符 |
用于父子组件间,子组件需要改变父组件值时 |
provide /inject |
多用于高阶组件库,平常开发一般不使用,不利于代码维护 |
中央事件总线 | 适用于跨级或兄弟组件间通信。 |
VueX |
用于大型单页面中多个组件需要共享同一状态时。 |