Vue组件通信详解:事件总线Event Bus的使用方法
Vue组件通信详解:事件总线Event Bus的使用方法
Vue组件通信是Vue开发中的重要概念,其中事件总线(Event Bus)是一种常用的跨组件通信方式。本文将详细介绍Vue组件通信的各种方式,并重点阐述事件总线的使用方法及其优缺点。
Vue组件通信概述
Vue组件通信是指在Vue框架中,不同组件之间传递数据和触发行为的过程。主要场景包括父子组件通信、隔代组件通信和兄弟组件通信。常见的通信方式有:
props / $emit
:适用于父子组件通信$parent / $children
:适用于父子组件通信ref / $refs
:适用于父子组件通信provide / inject
:适用于隔代组件通信- Vuex / Pina:适用于复杂场景的全局状态管理
- Event Bus:适用于各种组件通信场景
$attrs / $listeners
:适用于隔代组件通信
本文将重点介绍Event Bus这种通信方式的使用。
什么是事件总线(Event Bus)
事件总线是一种用于组件间通信的模式,通常用于解决组件之间的解耦和简化通信的问题。它是一个能够触发和监听事件的机制,使得组件能够在不直接依赖彼此的情况下进行通信。事件总线可以是一个全局的单例对象,也可以是一个基于发布-订阅模式的实现。
订阅-发布模式
订阅-发布模式由发布者和订阅者构成:
- 发布者(Publisher):负责发布(广播)消息或事件的对象。当发布者的状态发生变化时,它会通知所有已订阅的对象。
- 订阅者(Subscriber):订阅发布者的消息或事件的对象。订阅者通过注册自己的回调函数(或观察者)来接收发布者的通知。
实现步骤如下:
- 发布者维护一个订阅者列表(比如:数组),用于存储所有订阅了它的对象。
- 订阅者向发布者注册自己的回调函数(或观察者)。
- 当发布者的状态发生变化时,它会遍历订阅者列表,调用每个订阅者的回调函数,通知它们状态的变化。
事件总线的优缺点
优点
- 跨组件通信:可以方便地实现非父子组件之间的通信,不需要在组件之间建立直接的关联。
- 全局通信:事件总线通常是全局性的,能够在整个应用中的任何地方进行通信,适用于全局状态的传递和应用的整体控制。
- 解耦组件:能够实现组件之间的解耦,使得组件之间不需要直接引用或依赖彼此,提高了代码的灵活性和可维护性。
- 简化通信:对一些简单的通信需求,事件总线提供了一种相对简单的方式,避免了通过 props 和回调函数传递数据时的繁琐操作。
缺点
- 全局状态管理:使用事件总线可能引入全局状态,导致应用状态变得难以追踪和理解,特别是在大型应用中。
- 难以调试:全局性的事件监听和触发可能使得追踪代码执行流程和调试变得更加困难,尤其是在复杂的应用场景下。
- 不明确的数据流向:使用事件总线时,数据的流向相对不明确,可能增加代码的复杂性,使得应用程序的数据流变得更加难以理解。
- 潜在的性能问题:大量的全局事件监听和触发可能导致性能问题,尤其是在频繁触发事件的情况下。
- 安全性问题:由于事件总线是全局的,可能存在安全风险,例如某个组件监听了不应该被其它组件触发的敏感事件。
简单总结:小型应用或简单的场景,事件总线适用,但在大型应用或需要更严格状态管理和调试的情况下,可考虑使用更复杂的状态管理工具,如 Vuex 或 Redux。
实现与使用
Vue2项目
第一步:新建文件eventBus.js
const eventBus = new Vue({
methods: {
emit(event, ...args) { // 发布
this.$emit(event, ...args);
},
on(event, callback) { // 接收
this.$on(event, callback);
},
off(event, callback) { // 销毁
this.$off(event, callback);
}
}
});
export default eventBus;
第二步:在main.js
中全局注册
import Vue from "vue";
// ...
import eventBus from "./utils/eventBus.js"; // 引入封装的方法
// ...
Vue.prototype.$bus = eventBus; // 挂载载Vue实例上
// ...
第三步:使用
home.vue组件中发布数据
<template>
<div class="hello">
<h1>Home Page</h1>
<el-button type="primary" @click="handleClick">
点击将数据传到about组件
</el-button>
</div>
</template>
<script>
export default {
data() {
return {
hasSendMessage: false
};
},
methods: {
handleClick() {
this.hasSendMessage = true;
this.$bus.emit("fromHome", {
msg: "this is form Home page message ~ "
});
}
},
destroyed() {
this.hasSendMessage && this.handleClick(); // 这里很重要!!!
this.hasSendMessage = false;
}
};
</script>
about.vue 接收数据
<template>
<div class="hello">
<h1>About Page</h1>
<div>接收到的数据: {{ message }}</div>
</div>
</template>
<script>
export default {
data() {
return {
message: ""
};
},
created() {
this.$bus.on("fromHome", data => {
console.log("create receive data: ", data);
this.message = data.msg;
});
},
destroyed() {
this.$bus.off("fromHome"); // 这里也需要注意,组件销毁时同时销毁eventBus事件,以免消耗性能
}
};
</script>
可能遇到的问题:
- 接收数据的组件收到了传递的内容,但是在页面上没有展示。
原因:与vue组件的加载顺序和生命周期有关。
解决:在分发消息的组件destroyed()生命周期钩子函数中再调用emit() 分发消息。(上述vue2示例中,也重点标注了要注意的地方)。
vue2 父组件和子组件的生命周期的执行顺序:
1)创建 / 挂载过程:: 父组件beforeCreate -> 父组件created -> 父组件beforeMount -> 子组件beforeCreate -> 子组件created -> 子组件beforeMount -> 子组件mounted -> 父组件mounted
2)更新过程:父组件数据发生变化,导致子组件需要重新渲染时 : 父组件beforeUpdate -> 子组件beforeUpdate -> 子组件updated -> 父组件updated
3)销毁过程: 当父组件被销毁时: 父组件beforeDestroy -> 子组件beforeDestroy -> 子组件destroyed -> 父组件destroyed
Vue3项目
Vue3中默认情况下是没有内置的 EventBus,也就是说Vue3 没有像 Vue2 那样的 $on()
和 $emit()
的全局事件系统。
因为在 Vue 3 中,官方推荐使用 Composition API 以及更灵活的函数式组件,同时提倡使用更明确的通信方式。比如props
、emits
、provide
/inject
、pinia。
一般在vue3使用eventBus功能常常借用第三方库,比如本文在接下来介绍的mitt 和 vue3-eventbus等。
方式1:使用 mitt
mitt
是一个简单而强大的事件总线库,用于在组件之间进行事件的发布和订阅。
它提供了一种简洁的方式来实现组件之间的通信,而无需借助 Pinia 或其他状态管理库。
安装:
npm install --save mitt
新建文件:
eventBus.js
:
import mitt from "mitt";
const eventBus = new mitt();
export default eventBus;
mitt
主要方法:
emit(name,data) // 触发事件,两个参数:name:触发的方法名,data:需要传递的参数
on(name,callback) // 绑定事件,两个参数:name:绑定的方法名,callback:触发后执行的回调函数
off(name) // 解绑事件,一个参数:name:需要解绑的方法名
home.vue发送数据
<template>
<h1>Home page</h1>
<a-button type="primary" @click="handleClick">
点击将消息发送给About组件
</a-button>
</template>
<script lang="ts" setup>
import { onUnmounted, ref } from "vue";
import eventBus from "../utils/eventBus";
const hasClicked = ref(false);
const handleClick = function () {
eventBus.emit("homeMsg", "this is message publish by home page. ");
hasClicked.value = true;
};
onUnmounted(() => { // 此处代码也很重要!有可能导致接收数据的组件收不到,原因也是和vue3组件生命周期执行顺序由关
hasClicked.value && handleClick();
hasClicked.value = false;
});
</script>
about.vue接收数据
<template>
<h1>About page</h1>
<div>receive Message: {{ receiveMsg }}</div>
</template>
<script setup lang="ts">
import { ref, onBeforeMount, onUnmounted } from "vue";
import eventBus from "../utils/eventBus";
const receiveMsg = ref("");
// 此处监听的位置需要注意!
onBeforeMount(() => {
eventBus.on("homeMsg", (content: string) => {
receiveMsg.value = content;
console.log("received message from home: ", content);
});
});
onUnmounted(() => {
eventBus.off("homeMsg");
});
</script>
方式2:使用 vue3-eventbus
安装:
npm install --save vue3-eventbus
挂载:
main.js
import eventBus from 'vue3-eventbus'
// ...
app.use(eventBus)
home.vue发送数据
<template>
<h1>Home page</h1>
<a-button type="primary" @click="handleClick">
点击将消息发送给About组件
</a-button>
</template>
<script lang="ts" setup>
import { onUnmounted, ref } from "vue";
import eventBus from "vue3-eventbus"; // 对比mitt使用区别,就是入口不一样而已,其他都一样
const hasClicked = ref(false);
const handleClick = function () {
eventBus.emit("homeMsg", "this is message publish by home page. ");
hasClicked.value = true;
};
onUnmounted(() => {
hasClicked.value && handleClick();
hasClicked.value = false;
});
</script>
about.vue接收数据
<template>
<h1>About page</h1>
<div>receive Message: {{ receiveMsg }}</div>
</template>
<script setup lang="ts">
import { ref, onBeforeMount, onUnmounted } from "vue";
import eventBus from "vue3-eventbus";
const receiveMsg = ref("");
onBeforeMount(() => {
eventBus.on("homeMsg", (content: string) => {
receiveMsg.value = content;
console.log("received message from home: ", content);
});
});
onUnmounted(() => {
eventBus.off("homeMsg");
});
</script>
vue3项目中使用注意点:要在合适的生命周期钩子中emit数据,否则很容易出现接收数据的组件中on事件监听不到数据。
- (重要知识点 ‼️)vue3路由跳转时,页面1和页面2的生命周期执行顺序为:
旧页面onBeforeUnmount -> 新页面setup -> 新页面onBeforeMount -> 旧页面onUnmounted -> 新页面onMounted
总结
用eventBus进行组件之间的数据传递,需要注意的有三点:
- 事件名必须保持统一;
- 派发数据的组件A内 emit 事件应在
beforeDestory
生命周期内; - 接收数据的组件B内on事件记得要销毁。