写在前面

组件间通信简单来说就是组件间进行数据传递。就像我们日常的打电话,就是通讯的一种方式,你把话说给我听,我把话说给你听,说的话就是数据。电话就是通讯方式的一种。无论是 Vue 还是 React ,都得进行组件间通信。Vue 组件间通信的方式介绍如下。

1. 父子间通信

最常见的就是父子之间的通信,通信是双向的数据传递。

1.1 父组件 —> 儿子组件

父组件向儿子组件传递数据的方式就是 通过 Prop 向子组件传递数据

  1. //child.vue
  2. <template>
  3. <div>
  4. 我是儿子,我收到来自父亲的数据为 {{value}}
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. props:{
  10. value: String
  11. }
  12. }
  1. //App.vue
  2. <template>
  3. <div id="app">
  4. <Child :value="x" />
  5. </div>
  6. </template>
  7. <script>
  8. import Child from './components/Child'
  9. export default {
  10. data(){
  11. return {
  12. x: 'hi,child'
  13. }
  14. },
  15. components:{
  16. Child
  17. }
  18. }
  19. </script>

1.2 儿子组件 —> 父组件

儿子组件向父组件传递数据的方式就是通过子组件内 $emit 触发自定义事件,子组件使用时 v-on 绑定监听自定义事件。

这里的 v-on 事件通信是在子组件使用时作为子组件的事件属性自动进行监听的。

因此儿子组件向父组件传递数据,依赖于子组件使用时的自定义事件属性。

  1. //child.vue
  2. <template>
  3. <div>
  4. 我是儿子,我收到来自父亲的数据为 {{value}}
  5. <button @click="sayHi">
  6. 向父组件打招呼
  7. </button>
  8. </div>
  9. </template>
  10. <script>
  11. export default {
  12. props:{
  13. value: String
  14. },
  15. methods:{
  16. sayHi(){
  17. this.$emit('sayHi','hi,parent!');
  18. }
  19. }
  20. }
  21. </script>
  1. //App.vue
  2. <template>
  3. <div id="app">
  4. 我是父组件,我收到子组件传来的数据为 {{y}}
  5. <Child :value="x" @sayHi="y = $event"/>
  6. </div>
  7. </template>
  8. <script>
  9. import Child from './components/Child'
  10. export default {
  11. data(){
  12. return {
  13. x: 'hi,child',
  14. y: ''
  15. }
  16. },
  17. components:{
  18. Child
  19. }
  20. }
  21. </script>

Vue 如何实现组件间通信 - 图1

2. 爷孙间通信

爷孙间通信,可以使用两次 v-on 通信,爷爷爸爸通信,然后爸爸儿子通信。
也可使用下方的任意组件间通信的方式。

3. 任意组件间通信

任意组件间通信就不再区分是 A 向 B 通信,还是 B 向 A 通信,而是通用的方式,谁想发送数据就使用对应的 API 发送数据,谁想要接收什么数据,就使用对应的 API 接收。

任意组件间通信有两种方式,一种是使用 EventBus 发布订阅模式通信,一种是使用 Vuex 通信。

3.1 EventBus

EventBus ,从字面意思理解就是事件公交车,所有触发的事件传递的数据都从前门上车保存到公交车上,然后通过监听对应事件提供的出口让对应的事件数据下车。

EventBus,实际意思是发布和订阅模式,就是谁想把数据传递出去,就要通过触发自定义事件的 API 进行数据的发布;谁需要接收该数据信息的,就通过事件监听的 API 进行数据的监听,一旦检测到监听的数据发布出来,就会接收,这就是数据的订阅。

EventBus 通信方式最重要是搞明白发布和订阅的接口 API,在 Vue 中,Vue 实例有提供两个接口,即 $emit$on ,因此可以新创建一个空的 Vue 实例,来获得这两个接口。

  1. const eventBus = new Vue();
  2. eventBus.$emit(eventName, […args]) //发布事件
  3. eventBus.$on(event, callback) //订阅事件

实例如下:

  1. // eventBus.js
  2. import Vue from 'vue'
  3. export const eventBus = new Vue();
  1. //child
  2. <template>
  3. <div>
  4. 我是儿子,我收到来自父亲的数据为 <strong>{{value}}</strong>
  5. <button @click="sayHi">
  6. 向父组件打招呼
  7. </button>
  8. <button @click="sibling">
  9. 向兄弟组件打招呼
  10. </button>
  11. </div>
  12. </template>
  13. <script>
  14. import {eventBus} from '../eventBus.js'
  15. export default {
  16. props:{
  17. value: String
  18. },
  19. methods:{
  20. sayHi(){
  21. this.$emit('sayHi','hi,parent!');
  22. },
  23. sibling(){
  24. eventBus.$emit('sibling','hi,brother');
  25. }
  26. }
  27. }
  28. </script>
  29. <style scoped>
  30. strong{
  31. color: red;
  32. }
  33. </style>
  1. //sibling
  2. <template>
  3. <div>
  4. 我是兄弟组件,我收到来自儿子组件的数据信息为 <strong>{{x}}</strong>
  5. </div>
  6. </template>
  7. <script>
  8. import {eventBus} from '../eventBus.js'
  9. export default {
  10. data(){
  11. return {
  12. x: ''
  13. }
  14. },
  15. mounted(){
  16. eventBus.$on('sibling', (msg)=>{
  17. this.x = msg;
  18. })
  19. }
  20. }
  21. </script>
  22. <style scoped>
  23. strong{
  24. color: green;
  25. }
  26. </style>
  1. //parent
  2. <template>
  3. <div id="app">
  4. 我是父组件,我收到子组件传来的数据为 <strong>{{y}}</strong>
  5. <Child :value="x" @sayHi="y = $event"/>
  6. <Sibling></Sibling>
  7. </div>
  8. </template>
  9. <script>
  10. import Child from './components/Child'
  11. import Sibling from './components/Sibling'
  12. export default {
  13. data(){
  14. return {
  15. x: 'hi,child',
  16. y: ''
  17. }
  18. },
  19. components:{
  20. Child,
  21. Sibling
  22. }
  23. }
  24. </script>
  25. <style scoped>
  26. strong{
  27. color: blue;
  28. }
  29. </style>

Vue 如何实现组件间通信 - 图2

关于 EventBus 这部分,可能存在这样一个疑问,既然 Vue 实例中都有 **$emit****$on**,为什么不直接用 **this.$emit** 触发事件, **this.$on** 接收事件呢?还非得要额外一个空实例 eventBus = new Vue() 。那是因为,Vue 中每个组件都是一个单独的 Vue 实例,你在这个 Vue 实例中触发该实例的 emit 事件,另外一个实例的 on 事件是接收不到的,不在一辆公交车上,怎么能进行事件通信呢?因此就必须要一个公共的公交车,也就是事件总线。

上述实例中的 eventBus 的使用方法是局部的 eventBus,谁要用到 eventBus 要自己手动引入。也可以将 eventBus 做成全局的,比如挂在 vue 的原型上。

  1. //main.js
  2. import Vue from 'vue'
  3. import App from './App.vue'
  4. Vue.config.productionTip = false
  5. Vue.prototype.$eventBus = new Vue();//添加这句,一定要在下方的 new Vue 前。
  6. new Vue({
  7. render: h => h(App),
  8. }).$mount('#app')
  1. //child
  2. sibling(){
  3. this.$eventBus.$emit('sibling','hi,brother');
  4. }
  1. //sibling
  2. mounted(){
  3. this.$eventBus.$on('sibling', (msg)=>{
  4. this.x = msg;
  5. })
  6. }

除了上述的添加属性到 Vue 原型的方式外,还可以使用 Object.defineProperty() 为 Vue 原型添加属性。

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. Vue.config.productionTip = false
  4. let eventBus = new Vue()
  5. Object.defineProperty(Vue.prototype,'$eventBus',{
  6. get(){
  7. return eventBus
  8. }
  9. })
  10. new Vue({
  11. render: h => h(App),
  12. }).$mount('#app')

3.2 Vuex

Vue 组件间的通信也可使用专门为 vue.js 应用程序开发的状态管理模式:Vuex。Vuex 的使用比较复杂,详细可见 Vuex 博客。Vuex 适用于大型的复杂的 Vue 项目的状态管理。对于一些中小型的应用程序,可以根据 Vuex 的原理自定义 store 模式进行状态管理,vue 自定义状态管理,可详见 Vue 简单状态管理—store模式 博客。

无论是 Vuex 还是 自定义 store模式 ,其实现组件间通信的原理都是通过共享数据的方式实现的。组件间使用相同的数据源,当一个组件改变数据时,另一个组件依赖的数据源也就改变了。

参考博客

vue篇之事件总线(EventBus)