如下图所示, 子组件 和 子组件 间可以通过 共享一个父组件 或全局事件总线 / 状态管理库 实现数据通信
堂兄弟 🎈 组件间:建议采用 共享一个父组件的方案
表兄弟组件间:建议采用 全局事件总线 / 状态管理库的方案
兄弟关系图
如下图所示,D 组件和 E 组件是堂兄弟,而 F 组件与 D/E 组件是表兄弟。
# 一、全局事件总线
Vue.js 框架提供了一个全局事件总线(Global Event Bus),也称为 Vue 实例事件系统。
该事件总线允许在应用程序中的任何组件之间进行通信,无论它们是否具有父子关系
注意
- 在 Vue.js 2.x 及更早版本中,EventBus 是一个基于 Vue 实例的解决方案
- 在 Vue.js 3.x 及以上版本中,全局事件 API 已经被重构,EventBus 被视为一种过时的模式,应使用新的全局事件钩子函数来代替(或者使用官方推荐的 mitt (opens new window)、tiny-emitte 库)
原因
其实我们细细想来,vue3 去除 EventBus(去除了$on
、$off
、$once
等)也是非常合理的。
在 vue 中,数据共享的方案先如今已经非常多了:
- 父子间:props、
$emit
等(vue3 中子传父不能使用$on
、$off
方式也没关系,反正基本不用) - 兄弟间:共享父组件、状态管理库(由于 pinia 的盛行,vue3 中的 EventBus 显得有点多余)
- 其他:
- 上下游(provide、inject)
- 边界处理($refs 等)
# 1、EventBus
EventBus 表示事件总线,它实际上是一个 Vue 实例,被用作一个中央事件处理器,所有其他组件都可以通过订阅和发布事件来与它通信。
- 方式 1
直接 new 一个崭新的 vue 实例对象作为 EventBus
// eventBus.js
import Vue from 'vue'
// 向外共享Vue的实例对象
export default new Vue()
- 方式 2 👍
在 vm 的原型上挂载一个$bus 作为 EventBus
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: (h) => h(App),
beforeCreate() {
// 这样vue原型上的$bus就有了和vm/vc一样的方法和属性了
Vue.prototype.$bus = this
}
}).$mount('#app')
# 2、数据共享
我们在自定义事件部分已经了解到了:
$on
:监听当前父组件为子组件定义的的自定义事件/事件回调$emit
:触发父组件为当前子组件绑定的自定义事件/事件回调
在 vue 中我们可以基于上面两个知识点实现结合 EventBus,就可以实现各个组件间的数据通信了。
下面我们以 HomeView 组件向 AboutWorld 组件表白为例:
- AboutWorld.vue
<template>
<div class="home">
<h3>AboutWorld组件内容</h3>
</div>
</template>
<script>
export default {
name: 'AboutWorld',
mounted() {
this.$bus.$on('homeToAbout', (val) => {
console.log('我监听到你触发了$emit,我收到的数据为:\n', val)
})
},
// 注意事项
beforeDestroy() {
this.$bus.$off('homeToAbout')
}
}
</script>
- Send.vue
<template>
<div class="home">
<h3>HomeView组件内容</h3>
<button @click="sendMsg">向AboutWorld表白</button>
<br /><br />
子组件内容如下:
<hello-world></hello-world>
</div>
</template>
<script>
import HelloWorld from '../components/HelloWorld'
export default {
name: 'HomeView',
components: {
HelloWorld
},
data() {
return {
interact: '你可以教我包饺子吗?我有点笨,做什么都容易露馅,喜欢你也是~'
}
},
methods: {
sendMsg() {
this.$bus.$emit('homeToAbout', this.interact)
}
}
}
</script>
# 二、vue 的数据流
# 1、依赖注入 ✨
在父组件只要声明了provide
,在其子组件,孙组件,曾孙组件等能形成上下游关系的组件中交互。
无论多深都能通过
inject
来访问provide
中的数据。
提示
这个一般使用较少,如果在 vue 中使用依赖注入,表明当前项目存在大量的组件嵌套,是不利于维护的。
但在 react 中由于高阶组件的存在,使用依赖注入的概率会高于 vue。
<script>
export default {
// provide 提供给后代✨组件的数据/方法
// provide: {
// message: 'provided by father'
// }
provide() {
return {
message: 'provided by father'
}
}
}
</script>
<script>
export default {
// 使用 inject 选项来接收由provide提供的、当前组件✨需要的数据/方法
inject: ['message']
}
</script>
使用示例
- provide
<template>
<div>
<p>{{ title }}</p>
<son></son>
</div>
</template>
<script>
import Son from "./son"
export default {
name: 'Father',
components: { Son },
// provide 提供给后代✨组件的数据/方法
provide: {
message: 'provided by father'
},
data () {
return {
title: '父组件'
}
},
methods: { ... }
}
</script>
- 过渡:
<template>
<div>
<p>{{ title }}</p>
<grand-son></grand-son>
</div>
</template>
<script>
import grandSon from './grandSon'
export default {
name: 'Son',
components: { grandSon },
data() {
return {
title: '子组件'
}
}
}
</script>
- inject
<template>
<div>
<p>message:{{ message }}</p>
</div>
</template>
<script>
export default {
name: "GrandSon",
// 使用 inject 选项来接收由provide提供的、当前组件✨需要的数据/方法
inject: [ "message" ],
data () {
return {
title: '孙组件'
}
},
methods: { ... }
};
</script>
拓展:provide 与 响应式数据 🤔
通过测试,我们发现修改 provide 中的数据时,inject 中使用的数据并不会响应式更新。
解决方案:
我们使用一些响应式 API 来完成功能:比如 computed 函数
<script>
export default {
data() {
return {
dataToProvide: {
count: 0
}
}
},
provide() {
return {
dataToProvide: Vue.observable(this.dataToProvide)
}
}
}
</script>
# 2、处理边界
- $refs:访问子组件的实例
这个可以用于父组件触发子组件方法的场景中
- $root :访问根实例 *
- $parent:访问父组件的实例 *
# 3、状态管理库
vuex、pinia、redux
# 二、消息发布订阅
方式 1:
使用上面的 Vue 实例事件系统
方式 2:
使用 pubsub-js 包即可