在 Vue 3 中,由于创建 Vue 实例的方式发生了变化,并且推荐使用 Composition API,事件总线的实现方式也与 Vue 2 有所不同。
一、Vue 3 事件总线的核心变化
在 Vue 2 中,我们通常 new Vue() 来创建事件总线。但在 Vue 3 中:
- Vue 构造函数不再直接导出,取而代之的是
createApp。 -
Vue 实例不再是一个事件发射器,它移除了
$on,$off, 和$once方法。
因此,我们不能再用 new Vue() 来创建事件总线。
二、Vue 3 实现事件总线的推荐方案
有几种方式可以在 Vue 3 中实现事件总线模式,这里介绍最主流和推荐的两种。
方案一:使用第三方库mitt(最推荐)
mitt 是一个小巧(约 200 bytes)、功能完整的的事件总线库,它不依赖 Vue,但可以完美地与 Vue 3 集成。这是目前 Vue 社区公认的最佳实践。
1. 安装 mitt
npm install mitt
2. 创建事件总线
创建一个单独的文件(如 event-bus.js):
// event-bus.js import mitt from 'mitt'; // 创建一个 mitt 实例并导出 const emitter = mitt(); export default emitter;
3. 在组件中使用
发布事件(发送消息):
使用 emitter.emit(eventType, data)
import { ref } from 'vue';
import emitter from './event-bus.js'; // 导入事件总线
const message = ref('Hello from Vue 3!');
const sendMessage = () => {
// 发布一个名为 'messageEvent' 的事件,并携带数据
emitter.emit('messageEvent', message.value);
// 也可以发布没有数据的事件
// emitter.emit('someNotification');
};
订阅事件(接收消息):
使用 emitter.on(eventType, callback)
关键点: 在 Composition API 的 setup 中,需要在 onMounted 生命周期中订阅,并在 onUnmounted 中取消订阅,以防止内存泄漏。
收到消息: {{ receivedMessage }}import { ref, onMounted, onUnmounted } from 'vue'; import emitter from './event-bus.js'; // 导入事件总线 const receivedMessage = ref(''); // 定义处理事件的函数 const handleMessage = (data) => { receivedMessage.value = data; console.log('收到数据:', data); }; // 组件挂载后订阅事件 onMounted(() => { emitter.on('messageEvent', handleMessage); }); // 组件卸载前取消订阅 onUnmounted(() => { emitter.off('messageEvent', handleMessage); });
mitt 的其他常用 API:
-
emitter.all: 一个 Map,可以查看所有事件和处理函数。 -
emitter.off(eventType, handler): 移除特定事件的特定监听器。 -
emitter.emit(eventType): 触发事件,可不传数据。
方案二:使用provide/inject实现一个简单的事件总线
如果你不想引入第三方库,并且应用场景比较简单,可以利用 Vue 3 的 provide 和 inject 特性,在根组件提供一个响应式对象作为事件总线。
1. 在根组件(如 App.vue)提供事件总线
import { ref, provide } from 'vue';
import PublisherComponent from './components/PublisherComponent.vue';
import SubscriberComponent from './components/SubscriberComponent.vue';
// 1. 创建一个响应式对象来存储事件和监听器(简化版)
const eventBus = ref({});
// 2. 创建事件总线的方法
const eventBusMethods = {
$on(event, callback) {
if (!eventBus.value[event]) {
eventBus.value[event] = [];
}
eventBus.value[event].push(callback);
},
$emit(event, ...args) {
if (eventBus.value[event]) {
eventBus.value[event].forEach(callback => callback(...args));
}
},
// 可以自行实现 $off 等功能
};
// 3. 将事件总线方法提供给所有子组件
provide('eventBus', eventBusMethods);
2. 在子组件中注入并使用
发布者组件:
import { inject } from 'vue';
// 注入事件总线
const eventBus = inject('eventBus');
const $emitMessage = () => {
eventBus.$emit('myEvent', '一些数据');
};
订阅者组件:
消息: {{ message }}import { ref, inject, onMounted, onUnmounted } from 'vue'; // 注入事件总线 const eventBus = inject('eventBus'); const message = ref(''); const handleEvent = (data) => { message.value = data; }; onMounted(() => { eventBus.$on('myEvent', handleEvent); }); // 注意:这个简易实现需要更复杂的逻辑来实现 $off,这里省略了取消订阅 // onUnmounted(() => { ... });
三、Vue 3 事件总线 vs. 状态管理工具(如 Pinia)
| 特性 | 事件总线(如 mitt) | 状态管理(如 Pinia) |
|---|---|---|
| 目的 | 组件间通信,传递事件和消息 | 全局状态管理,集中管理数据 |
| 数据流 | 单向或双向,通常是“一发一收” | 单向数据流,状态可预测 |
| 数据持久性 | 事件触发后即消失,不存储数据 | 数据存储在 Store 中,是持久的 |
| 适用场景 | 简单的、一次性的通知(如显示通知、表单提交完成) | 复杂的、需要共享和持久化的数据(如用户登录信息、购物车) |
| 调试 | 相对困难,事件是匿名的 | 非常方便,有 DevTools 支持 |
总结与建议
-
对于 Vue 3,强烈推荐使用
mitt来实现事件总线。它轻量、简单且完美契合 Vue 3 的理念。 - 将事件总线的创建逻辑放在一个独立的模块中(如
event-bus.js),便于管理和维护。 -
务必记住在组件卸载时(
onUnmounted)取消事件监听,这是避免内存泄漏的关键。 - 如果应用的数据流变得非常复杂,需要多个组件共享和修改同一份数据,请考虑使用 Pinia 这类专门的状态管理库,而不是滥用事件总线。事件总线更适合处理行为和通知,而非管理状态。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。
