Vue组件通信的八种方式(非父子组件之间的通信)

1,249 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

上篇文章我们总结了Vue组件通信之父子组件之间的通信方法。本文为您继续总结Vue组件通信之非父子组件之间的通信方法

$emit -> $on(evenBus)

Vue提供一种名为eventBus的事件总线用于搭建组件通信的桥梁,eventBus用来管理所有组件的通信。任何一个组件的通信都可以记录在eventBus中,因此我们认为它是vue组件的通信中心。在被记录之前,我们需要创建一个eventBus的实例用来接收和注册通信消息。

//event-bus.js

import Vue from  'vue'

export const EventBus =  new Vue()

假设我们有组件B和C 如下代码 BC之间可以称为兄弟组件关系

// ComponentA(父组件)
<template>
 <div class="fruit_shop">
 <component-b/>
 <component-c/>
 </div>
</template>
<script>
import componentB from './componentB.vue'
import componentC from './componentC.vue'
export default {
 name'ComponentA',
 components: { componentB,componentC },
  }
</script> 
// ComponentB(组件B)
<template>
 <div>
  <div>{{num}}</div>
  <button @click="addNum">点击++</button>  
 </div>
</template>
<script>
import {EventBusfrom './event-bus.js'
export default {
 data(){
  return{
   num:1
  }
 },
 methods:{
  addNum(){
  //向事件中心注册通信 plus即定义的通信名称,传递参数num
   EventBus.$emit('plus', {
    num:this.num++
   })
  }
 }
}
</script>

//ComponentC(组件C)
<template>
 <div>统计:{{count}}</div>
</template>
<script>
import { EventBus } from './event-bus.js'
export default {
 data() {
  return {
   count0
  }
 },
 
 mounted() {
 //从事件中心接收到名为plus的通信 并获取到通信之间的传递的参数
  EventBus.$on('plus'param => {
   this.count = this.count + param.num;
  })
 }
}
</script>

如上代码:组件B和组件C进行了通信,组件B传递了num参数给组件C,组件C接收到来自B发送的消息。

假如有一天需求变更你使用过的某个通信消息不再需要实现,那么我们可以从中央事件总线eventBus中删除掉

import { EventBus } from './event-bus.js'

EventBus.$off('plus',{})

总结: EventBus中央事件总线可以处理任何组件之间的通信,但对于大型vue项目而言,使用EventBus会造成维护困难,代码可读性较差的问题。

$attrs -> $listeners

上篇文章组件关系图片中 A-D 之间为祖孙关系。对于这种关系,我们可以使用provide的和inject进行通信,但缺点是无法进行动态传递。我们也可以通过 EventBus进行通信,但是对于这样一个层级使用eventBus会给协作开发的其他人员增加不必要的负担,可读性不强。因此为了解决该问题,Vue2.4中提供了$attrs$listeners的实现。

// ComponentA(父组件)
<template>
 <div class="info">
 <component-b  
   :name="name"
   :gender="gender"
   :job="job"
   :age="age"
   @judge="judge"
   desc="法律的生命不在逻辑而在于经验。"
 />
 </div>
</template>
<script>
import componentB from './componentB.vue'
export default {
 name'ComponentA',
 data(){
  return {
     name:'张三',
     gender:'男',
     job:'法外狂徒',
     age:'30'
  }
}
 components: { componentB },
 method:{
 judge(){
 this.name = '罗老板' 
   }
 }
}
</script> 
// ComponentB(B组件)
<template>
 <div class="show-info">
 <p>name: {{ name}}</p>
<p>componentB中的$attrs: {{ $attrs }}</p>
 <component-d 
 v-bind="$attr" />
 </div>
</template>
<script>
import componentD from './componentD.vue'
export default {
 name'ComponentB',
 inheritAttrs: false, /*是否关闭没有在组件props中定义的参数
 通俗来说就是如果没定义props的接收$attrs中将不保存上游组件传递过来的参数 */
 props:{
   name:{
     type: String, 
     required: true
   }
 },
 components: { componentD },
 },
 mounted(){
 console.log(this.$attrs)
 //{ "gender":"男","job":"法外狂徒","age":"30","desc":"法律的生命不在逻辑而在于经验。"}
 console.log(this.$listeners)
 //打印 judge方法
 }
</script> 
//ComponentD(D组件)
<template>
 <div class="from-info">
 <p>name: {{ name}}</p>
<p>ComponentD中的$attrs: {{ $attrs }}</p>
 </div>
</template>
<script>
export default {
 name'ComponentD',
 inheritAttrs: false,
 props:{
   job:{
     type: String, 
     required: true
   }
 },
 mounted(){
 console.log(this.$attrs)
  //{ "gender":"男","age":"30","desc":"法律的生命不在逻辑而在于经验。"}
 }
 </script>

我们在父组件中将一些信息传递到了B组件,B组件通过$attrs获取到了未被定义在props中的其他参数。 同时,我们又将在父组件中获取的参数传递给了B组件的子组件D。D组件定义了job这个props,在D组件中也打印出了未被props定义的参数。这意味着$attrs可以为我们查缺补漏(嘿嘿!) $listeners则是打印出所有上一级组件的实例方法

总结: $attrs 里存放的是父组件中绑定的非 Props 属性, $listeners里存放的是父组件中绑定的非原生事件。 $attrs$listeners很好的解决了跨级组件之间通信。

Vuex

Vuex又名状态管理。它采用集中式存储管理应用的所有组件的状态。对于各个组件的不同数据进行响应式的存储。

Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上

Vuex的几大模块

  1. state:用于数据的存储,是store中的唯一数据源
  2. getters:用于封装state中的值。进行二次的一个包装。常用于数据的筛选和多个数据的相关性计算
  3. mutations:改变state数据的唯一途径。无法进行异步操作
  4. actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
  5. modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

Vuex功能强大,本文只是简单介绍一下如何使用:

// ComponentA(父组件)
<template>
 <div class="shop">
 <component-b/>
 <component-c/>
 </div>
</template>
<script>
import componentB from './componentB.vue'
import componentC from './componentC.vue'
export default {
 name'ComponentA',
 components: { componentB,componentC },
  }
</script> 
// componentB(B组件)
<template>
 <div class="componentB">
  <h1>我是B组件</h1>
  <button @click="transformInfo">点击按钮传递信息给C</button>
  <p>因为你点了C所以发生了变化:{{cCom}}</p>
 </div>
</template>
<script>
 export default {
  data() {
   return {
    bCom'Hello,C组件,我是B组件'
   }
  },
  computed: {
   cCom() {
    // 从store里获取到C组件的数据
    return this.$store.state.cCom
   }
  },
  methods: {
   transformInfo() {
    // 触发sendMsg,将B组件的数据存放到store里去
    this.$store.commit('sendMsgC', {
     cComthis.bCom
    })
   }
  }
 }
</script>
// componentC(C组件)
<template>
 <div class="componentC">
  <h1>我是C组件</h1>
  <button @click="transformInfo">点击按钮传递信息给B</button>
  <p>因为你点了B所以发生了变化:{{bCom}}</p>
 </div>
</template>
<script>
 export default {
  data() {
   return {
    cCom'Hello,组件,我是A组件'
   }
  },
  computed: {
   bCom() {
    // 这里存储从store里获取的B组件的数据
    return this.$store.state.bCom
   }
  },
  methods: {
   transformInfo() {
    // 触发receiveAMsg,将A组件的数据存放到store里去
    this.$store.commit('sendMsgB', {
     bComthis.cCom
    })
   }
  }
 }
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
 // 初始化A和B组件的数据,等待获取
 bCom'',
 cCom''
}
 
const mutations = {
 sendMsgB(state, payload) {
  // 将B组件的数据存放于state
  state.bCom = payload.bCom
 },
 sendMsgC(state, payload) {
  // 将C组件的数据存放于state
  state.cCom = payload.cCom
 }
}
export default new Vuex.Store({
 state,
 mutations
})

Vuex非常适合各种组件关系之间的通信。他和eventBus十分相似,但是功能却比eventBus强大好几倍。 同时他也有非常明显的缺点。。刷新页面如果没有重新将数据存入vuex中 将会造成数据的丢失。因此使用vuex时一般需要配合本地存储(localStorage)使用。

总结:Vuex非常适合各组件关系之间的通信。搭配localStorage可以防止刷新后数据丢失问题。

localStorage / sessionStorage

localStorage 和 sessionStorage 虽不属于Vue的实现,但它们也可以实现组件之间的通信。

通过window.localStorage.getItem(key)获取数据

通过window.localStorage.setItem(key,value)存储数据

注意用JSON.parse() / JSON.stringify() 做数据格式转换

缺点:localStorage进行组件之间的通信不是响应式的数据。且使用localStorage进行通信会造成维护困难,代码可读性差等问题。因此不推荐单独使用localStorage进行组件通信。

总结

常见使用场景可以分为三类:

  1. 父子组件通信: props; $parent / $children;provide / inject ; ref ;$attrs / $listeners
  2. 兄弟组件通信: eventBus ; vuex
  3. 跨级通信:  eventBus;Vuex;provide / inject 、$attrs / $listeners

最后

Vue组件通信的相关文章到这里就结束了。如果有疑惑或者觉得不妥的地方,欢迎您提出宝贵的意见。

感谢您观看此篇博客,如果对您有帮助,希望能给个👍评论收藏三连!