• 深入浅出,带你看懂Vue组件间通信的8种方案


    前言

    Vue种组件通信的情况有多种,总结有以下4种情况:

    • 父子组件间通信
    • 兄弟组件间通信
    • 祖孙后代间通信
    • 无关系组件间通信

    8种解决方案

    1. 通过 props 传递
    2. 通过 $emit 触发自定义事件
    3. 使用 ref
    4. 使用 EventBus
    5. 使用 parent 或root
    6. 使用 attrs 与 listeners
    7. 使用 Provide 与 Inject
    8. 使用 Vuex

    props进行组件间通信

    Prop作为组件间通信的方式,并不是通用的,而是只能父子组件中使用。

    场景:父组件传递数据给子组件

    • 子组件设置props属性种,接收父组件传递过来的参数
    • 父组件在使用子组件标签中通过字面量来传递值

    具体什么样呢?

    子组件:

     props:{  
      // 字符串形式  
     name:String // 接收的类型参数  
      // 对象形式  
      age:{    
          type:Number, // 接收的类型为数值  
          defaule:18,  // 默认值为18  
         require:true // age属性必须传递  
      }  
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    父组件:

       
    
    
    • 1
    • 2

    这个很好理解,不懂的话直接上手试试。

    $emit 触发

    子组件通过$emit触发定义在父组件里面的自定义事件,他可以传两个值,第一个是自定义事件名,第二个是要传递的值。

    • 适用场景:子组件传递数据给父组件
    • 子组件通过$emit触发定义事件,$emit中可以携带两个参数(‘名字’,‘参数’)
    • 父组件绑定监听器获取到子组件传递过来的参数
     //子组件
     this.$emit('msg', good)  
    
    
    • 1
    • 2
    • 3

    //父组件中子组件
     

    
    ### 使用ref获取
    
    **使用场景:**`ref` 被用来给`DOM`元素或子组件注册引用信息。引用信息将会注册在父组件的 `$refs` 对象上。
    
    *   **父组件在使用子组件的时候设置**`ref`
    *   **父组件通过设置子组件**`ref`来获取数据
    
    **如果在**`Vue3`中,那`ref`的作用就还有另一种了。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

     
     this.$refs.msg

    
    ### EventBus,即全局事件总线
    
    > *   **全局事件总线是一种组件间通信的方式,适用于任意组件间通信。**
    > *   **相当于给每个组件做个代理,作为数据通信的中转站(可以理解为**`中间商`)。
    > *   **其本质是**`Vue`的实例对象,通过`$emit、$on、$off`发布、监听、关闭事件。
    > *   **一般放在**`Vue`的原型对象上。
    
    **为什么要放到**`Vue`的原型上呢???
    
    **看这样一张图(来自哔哩哔哩尚硅谷课堂)。**
    
    ![](https://a.ideaopen.cn/JanYork/tkgbnnkr.jpg)
    
    **我们组件间通信是不是至少要两个组件,此时每个组件是不是有自己的实例对象,每个对象都有自己的原型对象对不对?(原型不懂没办法,**`JS`没学好,可以去补课了)。
    
    **这张图是一个啥玩意呢?我稍微讲一讲。**
    
    `JS`是`基于对象`的弱类型语言,所以JS的任何玩意,基本上都是对象。
    
    **此时我们Vue他是一个框架,也是**`JS`写的,我们使用他时,必须要创建一个对象(也就是`new Vue`)。
    
    **这就是我们图的这一部分:**
    
    ![](https://a.ideaopen.cn/JanYork/VY9z2VhV.png)
    
    **这个**`Vue`的实例对象总是会经过原型链中的`Vue`原型对象。
    
    **我们组件也一样,**`Vue`中`每个`组件都有自己的实例对象,而每个实例对象都有自己的原型对象,而每个组件实例对象的原型`总是`要到达原型链中的Vue原型对象。
    
    ![](https://a.ideaopen.cn/JanYork/uUPbc9Nz.png)
    
    **最后,因为**`Vue`原型对象他仍然是一个对象,所以有会指向`Object`原型。
    
    > **如果不懂,直接去看看尚硅谷的这堂课吧,讲的很好。**
    
    **那此时我们这条原型链就清楚了,无论是**`Vue`对象还是组件对象,还是组件实例的原型对象,`都要经过Vue的原型对象`。
    
    ![](https://a.ideaopen.cn/JanYork/yeYebcXn.png)
    
    **所以,这个Vue原型,它啥都可以接触到,那它当作中间商更合适不过了。**
    
    **所以我们需要将这个全局事件总线(名字是$bus)挂载到原型:**
    
    
    • 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

    // main.js
     import Vue from ‘vue’
     import App from ‘./App.vue’
     
     // 将 b u s 挂载在 V u e 原型上,当然也可挂载到 W i n d o w 上,不太建议  V u e . p r o t o t y p e . bus挂载在Vue原型上,当然也可挂载到Window上,不太建议  Vue.prototype. bus挂载在Vue原型上,当然也可挂载到Window上,不太建议 Vue.prototype.bus = new Vue()
     
     new Vue({
       render: h => h(App)
     }).$mount(‘#app’)

    
    **但是有一个小问题,就是这里的Vue被new了两次,实际上是可以优化的。**
    
    
    • 1
    • 2
    • 3

    // main.js
     import Vue from ‘vue’
     import App from ‘./App.vue’
     
     new Vue({
       render: h => h(App)
       beforeCreate () {
         //利用beforeCreate钩子函数挂载 b u s ,这是比较好的写法    V u e . p r o t o t y p e . bus,这是比较好的写法      Vue.prototype. bus,这是比较好的写法   Vue.prototype.bus = this
      }
     }).$mount(‘#app’)

    
    **使用起来也很方便。**
    
    **使用**`this.$bus.$emit`发送事件,需要接受数据的组件用`this.$bus.$on`监听,当然不要忘了在`beforeDestory`钩子函数中,用`this.$bus.$off`解绑当前事件。
    
    `$off`解绑单个事件`this.$bus.$off('a')`,多个可以用数组`this.$bus.$off(['a', 'b'])`。
    
    **我演示一下:**
    
    ![](https://a.ideaopen.cn/JanYork/xRhxxaPI.png)
    
    > **不过我这个就是在同一个组件发送的消息,不同组件使用方法一样。**
    
    ### $parent 或 $root 实现兄弟组件通信
    
    **原理:通过共同的祖辈**`$parent`或者`$root`,作为中间商,搭建通信桥连
    
    **兄弟组件1**
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    this.$parent.on(‘msg’,this.msg)

    
    **兄弟组件2**
    
    
    • 1
    • 2
    • 3

    this.$parent.emit(‘msg’)

    
    > **就相当于,你哥哥有一个玩具,你想要,可能哥哥不给。**
    > 
    > **哥哥可能将玩具给了爸爸。**
    > 
    > **你去找爸爸要,爸爸给你了。**
    > 
    > **利用同一个祖辈来传递。**
    
    ### $attrs 与 $listeners 实现祖辈与子辈通信
    
    **用于祖先传递数据给子辈。**
    
    > *   **设置批量向下传属性**`$attrs`和 `$listeners`
    > *   **包含了父级作用域中不作为**`prop` 被识别、获取的特性绑定 ( class 和 style 除外)。
    > *   **可以通过**`v-bind="$attrs"` 传⼊内部组件
    
    **父组件调用子组件时,传递除了使用**`prop`接收的属性以外 (class 和 style 除外),都可以使用`$attrs`获取。若要多层级组件使用 `$attrs`,则需要在中间子组件使用`v-bind="$attrs"` ,才可以被访问,否则访问`$attrs`为空对象。
    
    不过——>**在vue3.0中 $listeners被移除!!!**
    
    > **在 Vue 3 的虚拟 DOM 中,事件监听器现在只是以 on 为前缀的 attribute,这样就成了 $attrs 对象的一部分,因此 $listeners 被移除了。**
    
    > **在 Vue 2 中,你可以使用 this.attrs 和 this.listeners 分别访问传递给组件的 attribute 和事件监听器。结合 inheritAttrs: false,开发者可以将这些 attribute 和监听器应用到其它元素,而不是根元素。**
    
    **实例(网上找了一个):**
    
    
    • 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

    // child:并未在props中声明foo  
     

    {{$attrs.foo}}

     
       
     // parent  
     
     // 给Grandson隔代传值,communication/index.vue  
       
       
     // Child2做展开  
       
       
     // Grandson使⽤  
     
       {{msg}}    
      ``` ### provide 与 inject 实现 * **在祖辈组件中定义**`provide`属性,传递对应的值 * **在子辈组件通过**`inject`接收祖辈组件传递过来的值 ```  //祖辈组件  provide(){        return {            msg:'Hello World'       }    }  
    • 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

    // 获取到祖辈组件传递过来的值  
     inject:[‘msg’]

    
    ### vuex实现全局通信
    
    **复杂关系的组件数据传递我们可以选则使用**`Vuex`作为媒介,当然,你想的话,基本上任何数据都可以。
    
    `Vuex`类似于一个存储数据的容器,而且是挂载全局的公用容器。
    
    ![](https://img-blog.csdnimg.cn/img_convert/f9d6718f793f1efcc82855d257ea38b1.png)
    
    > *   `state`用来存放共享变量的地方。
    > *   `getter`,可以增加一个`getter`派生状态,(相当于`store`中的计算属性),用来获得共享变量的值。
    > *   `mutations`用来存放修改`state`的方法。
    > *   `actions`也是用来存放修改state的方法,不过`action`是在`mutations`的基础上进行。常用来做一些异步操作。
    
    ### 总结
    
    *   **父子关系的组件数据传递选择**`props` 与 `$emit`进行传递,也可选择`ref`。
    *   **兄弟关系的组件数据传递可选择**`$bus`,其次可以选择`$parent`进行传递。
    *   **祖先与后代组件数据传递可选择**`attrs`与`listeners`或者 `Provide`与 `Inject`。
    *   **复杂关系的组件数据传递可以通过**`vuex`存放共享的变量。
    
    ### 扩展知识
    
    `Pinia` 是 `Vue.js` 的`轻量级`状态管理库,最近很受欢迎。它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库。
    
    `Pinia`的成功可以归功于其管理存储数据的独特功能(可扩展性、存储模块组织、状态变化分组、多存储创建等)。
    
    **另一方面,**`Vuex`也是为`Vue`框架建立的一个流行的状态管理库,它也是`Vue核心团队`推荐的状态管理库。 `Vuex`高度关注应用程序的`可扩展性`、开发人员的工效和信心。它基于与`Redux`相同的流量架构。
    
    **有兴趣的话,也可以试试**`Pinia`。
    
    **下次,来讲讲**`Pinia`是否可以完美替代`Vuex`,以及`Vuex`与`Pinia`的区别。
    
    • 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
  • 相关阅读:
    SLAM从入门到精通(gmapping建图)
    SpringCloud-4.服务网关(GateWay)
    云计算和跨境电商:数字化未来的基石
    android studio cmake生成.a文件(静态库)及调用(c c++)静态库.a
    【Java】springboot 枚举参数
    基于微信小程序的土地租赁的设计与实现
    SAP ABAP结构与内表的创建
    FRP之入门篇
    VR全景HDR拍摄教程
    一、爬虫基本概念
  • 原文地址:https://blog.csdn.net/qq_60750453/article/details/126578187