Vue2/3中事件总线 - EventBus 区别以及详细使用方法

Vue 事件总线

事件总线本质上还是解决各个组件之间的通信问题。

解决在各个不同层级关系的组件之间传值/通信,目前常见的方法有:

  • 项目中有多层组件传参可以使用 $attrs,可以使代码更加美观,更加简洁,维护代码的时候更方便;
<!-- Father.vue -->
<template>
  <!-- 给子组件传了两个参数,但是子组件没有用prop接收-->
  <child-comp :is-show="isOpen" :row="row"></child-comp>
</template>

<!-- Child.vue -->
<template>
  <div class='child-view'>
    <p>子组件</p>
    <GrandChild v-bind="$attrs"></GrandChild>
  </div>
</template>
<script>
import GrandChild from './GrandChild.vue'
export default {
      
      
  // 继承所有父组件的内容
  inheritAttrs: true,
  components: {
      
       GrandChild }
}
</script>

<!-- Grand.vue -->
<template>
  <div class='grand-child-view'>
    <p>孙子组件</p>
    <p>传给孙子组件的数据:{
   
   {row.name}} {
   
   {row.name !== undefined? '学习' : ''}} {
   
   {row.study}}</p>
  </div>
</template>

<script>
export default {
      
      
  // 不想继承所有父组件的内容,同时也不在组件根元素dom上显示属性
  inheritAttrs: false,
  // 在本组件中需要接收从父组件传递过来的数据,注意props里的参数名称不能改变,必须和父组件传递过来的是一样的
  props: {
      
      
    isShow: {
      
      
      type: Boolean,
      dedault: false
    },
    row: {
      
      
      type: Object,
      dedault: () => {
      
       }
    }
  }
}
</script>

(如果给组件传递的数据,组件不使用 props 接收,那么这些数据将作为组件的HTML元素的特性,这些特性绑定在组件的HTML根元素上)

  • 在复杂关系的组件之间,如果使用普通的父子组件传参 prop$emit$on 会很繁琐,特别是在兄弟组件和祖先-子孙后代组件这种情形下。
  • 如果使用 vuex 会大材小用,只是在这几个组件中使用,没必要使用 vuex;而且 vuex 是用作数据状态存储的,如果用来作状态的传递,这种state数据会很冗余。
  • 使用依赖注入( provideinject)。
  • 事件总线 EventBus

事件总线

eventBus 是一个很简单但是也很实用的方案,适合组件跨级传递消息,我也经常在项目中使用这种方式,推荐一下,大家可以去了解一下这种方式。【需要注意的是:使用事件总线 eventBus,使用不恰当的话,有可能会出现事件多次执行。】

下面是分别在 Vue2 和 Vue3 中实现事件总线

Vue2 中事件总线

// common/event-bus.js
import Vue from 'vue'
const EventBus = new Vue()
export const EventBridge = {
    
    
  eventName: {
    
    
  	closeLabel: 'closeLabel',
    openLabel: 'openLabel',
    showDetailLabel: 'showDetailLabel'
  },
  on(name, fn) {
    
    
  	EventBus.$on(name, fn)
  },
  emit(name, data) {
    
    
  	EventBus.$emit(name, data)
  },
  offOne(name) {
    
    
  	EventBus.$off(name)
  },
  // 移除所有的时间监听
  offAll() {
    
    
  	EventBus.$off()
  },
  getEventName(key, value) {
    
     // value是没有取到值时的默认值
  	return this.eventName[key] + value
  }
}

使用方法:

// a.vue
<script>
import {
    
     EventBridger } from '@/common/event-bus'
export default {
    
    
  methods: {
    
    
  	showDetail(data) {
    
    
      EventBridger.emit('showDetailLabel', data)
    }
  }
}
</script>

// b.vue
<script>
import {
    
     EventBridger } from '@/common/event-bus'
export default {
    
    
  mounted() {
    
    
    EventBridger.on('showDetailLabel', (data) => {
    
    
      this.handlerShowDetai(data)
    })
  },
  methods: {
    
    
    handlerShowDetai(data) {
    
    
      console.log(data)
    }
  }
}
</script>

【方式二、将事件总线挂载到全局实例app上】

// main.js
Vue.prototype.$EventBus = new Vue()
new Vue({
    
    
  router,
  store,
  render: h => h(App)
}).$mount('#app')

使用方法:

// a.vue
this.$EventBus.$emit('abc')

// b.vue
mounted() {
    
    
  this.$EventBus.$on('abc, (data) => {
    
    
    console.log(data)
	// do your things
  })
}

【方式三】
在方式二中,我们创建了空的 Vue 对象用做事件总线并挂载在我们的全局应用属性上。
也许我们还可以更简单一点。
简单场景直接用 this.$root.$emitthis.$root.$on 也是一样的,可以少初始化一个 Vue 对象。

Vue3 中事件总线

在 Vue3.0+ 的版本中,Vue 移除了 $on$off$once 方法;并且没有提供兼容的功能。详情查看文档的说明

在这里插入图片描述

在 Vue3 中,关于事件总线模式可以被替换为使用外部的、实现了事件触发器接口的库,例如 mitttiny-emitter,这是官方推荐的事件总线替换方案。

// eventBus.js
import emitter from 'tiny-emitter/instance'

export default {
    
    
  $on: (...args) => emitter.on(...args),
  $once: (...args) => emitter.once(...args),
  $off: (...args) => emitter.off(...args),
  $emit: (...args) => emitter.emit(...args),
}

它提供了与 Vue 2 相同的事件触发器 API。

在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。虽然在短期内往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼。根据具体情况来看,有多种事件总线的替代方案:

  • Prop 和事件应该是父子组件之间沟通的首选。兄弟节点可以通过它们的父节点通信。
  • Provide 和 inject 允许一个组件与它的插槽内容进行通信。这对于总是一起使用的紧密耦合的组件非常有用。
  • provide/inject 也能够用于组件之间的远距离通信。它可以帮助避免“prop 逐级透传”,即 prop 需要通过许多层级的组件传递下去,但这些组件本身可能并不需要那些 prop。
  • Prop 逐级透传也可以通过重构以使用插槽来避免。如果一个中间组件不需要某些 prop,那么表明它可能存在关注点分离的问题。在该类组件中使用 slot 可以允许父节点直接为它创建内容,因此 prop 可以被直接传递而不需要中间组件的参与。
  • 全局状态管理,比如 Vuex。

扩展阅读

关于provide和inject使用需要注意的是:

然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的 property 是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root 做这件事都是不够好的。如果你想要共享的这个 property 是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。

解决provide和inject的响应式的问题

默认情况下,provide/inject 绑定并不是响应式的。这一点很尴尬。

但是,在 Vue2 中,我们可以传入了一个可监听的对象,那么其对象的 property 还是可响应的。

什么是可监听的对象?
可监听的响应对象: Array, Object。

或者,在 Vue3 中,我们可以通过传递一个 ref propertyreactive 对象给 provide 来使其有响应式。

provide() {
    
    
    return {
    
    
      todoLength: Vue.computed(() => this.todos.length)
    }
}

为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 refreactive

setup() {
    
    
  const location = ref('North Pole')
  const geolocation = reactive({
    
    
    longitude: 90,
    latitude: 135
  })
  provide('location', location)
  provide('geolocation', geolocation)
}

当使用响应式 provide / inject 值时,建议尽可能将对响应式 property 的所有修改限制在定义 provide 的组件内部。

// 提供 provide 的组件
methods: {
    
    
    updateLocation() {
    
    
      this.location = 'South Pole'
    }
}

然而,有时我们需要在注入数据的组件内部更新 inject 的数据。在这种情况下,我们建议 provide 一个方法来负责改变响应式 property

【父组件自己提供一个可以更改属性的方法,provide 出来,子孙组件调用这个方法来修改 property。】

const updateUserLocation = inject('updateLocation')

最后,如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly

provide('location', readonly(location))

—————————— 【正文完】——————————

前端学习交流群,想进来面基的,可以加群: 685486827832485817
Vue学习交流 React学习交流

写在最后: 约定优于配置 —— 软件开发的简约原则

——————————【完】——————————

我的:
个人网站: https://neveryu.github.io/neveryu/
Github: https://github.com/Neveryu
新浪微博: https://weibo.com/Neveryu
微信: miracle421354532

更多学习资源请关注我的新浪微博…好吗

猜你喜欢

转载自blog.csdn.net/csdn_yudong/article/details/121982548
今日推荐