靖江市本地信息网

Vue3中的事件总线详解

2026-04-04 10:04:02 浏览次数:1
详细信息

事件总线是一个用于组件间通信的模式,尤其适用于非父子组件跨层级组件之间的通信。Vue 3 官方推荐使用 mitttiny-emitter 等第三方库替代 Vue 2 中的 $emit$on 事件总线模式,因为 Vue 3 移除了 $on$off 等方法。

一、为什么 Vue 3 不再内置事件总线?

Vue 2 中可通过 new Vue() 创建事件总线:

// Vue 2 方式
export const eventBus = new Vue();

但在 Vue 3 中:

二、使用 mitt 实现事件总线(推荐)

1. 安装 mitt

npm install mitt
# 或
yarn add mitt

2. 创建事件总线

方式一:全局事件总线
// eventBus.js
import mitt from 'mitt';

const emitter = mitt();
export default emitter;
方式二:在应用层提供事件总线(依赖注入)
// main.js
import { createApp } from 'vue';
import mitt from 'mitt';
import App from './App.vue';

const emitter = mitt();
const app = createApp(App);

// 作为全局属性(不推荐大量使用)
app.config.globalProperties.$emitter = emitter;

// 或使用 Provide/Inject
app.provide('emitter', emitter);

app.mount('#app');

3. 使用示例

发送事件
<!-- ComponentA.vue -->
<template>
  <button @click="sendMessage">发送消息</button>
</template>

<script setup>
import emitter from '@/eventBus';

const sendMessage = () => {
  emitter.emit('message', { text: 'Hello from Component A!' });
};
</script>
接收事件
<!-- ComponentB.vue -->
<template>
  <p>接收的消息:{{ message }}</p>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import emitter from '@/eventBus';

const message = ref('');

// 监听事件
const handleMessage = (data) => {
  message.value = data.text;
};

onMounted(() => {
  emitter.on('message', handleMessage);
});

// 清理监听
onUnmounted(() => {
  emitter.off('message', handleMessage);
});
</script>
监听所有事件
emitter.on('*', (type, data) => {
  console.log(`事件类型:${type}`, data);
});
一次性监听
emitter.once('message', (data) => {
  console.log('只触发一次', data);
});

三、使用 Provide/Inject 实现事件通信

Vue 3 的响应式系统结合 Provide/Inject 也可实现类似功能:

// eventService.js
import { ref, provide, inject } from 'vue';

const createEventService = () => {
  const listeners = new Map();

  const emit = (event, data) => {
    const eventListeners = listeners.get(event);
    if (eventListeners) {
      eventListeners.forEach(callback => callback(data));
    }
  };

  const on = (event, callback) => {
    if (!listeners.has(event)) {
      listeners.set(event, new Set());
    }
    listeners.get(event).add(callback);
  };

  const off = (event, callback) => {
    const eventListeners = listeners.get(event);
    if (eventListeners) {
      eventListeners.delete(callback);
    }
  };

  return { emit, on, off };
};

// 提供事件服务
export const useEventService = () => {
  const service = createEventService();

  const provideEventService = () => {
    provide('eventService', service);
  };

  const injectEventService = () => {
    const service = inject('eventService');
    if (!service) throw new Error('Event service not provided');
    return service;
  };

  return { provideEventService, injectEventService };
};

四、最佳实践建议

1. 使用场景

2. 注意事项

// 1. 及时清理监听器,避免内存泄漏
onUnmounted(() => {
  emitter.off('event', handler);
});

// 2. 使用命名规范避免事件冲突
emitter.emit('user:login', data);
emitter.emit('cart:update', data);

// 3. 大型项目优先使用 Pinia 进行状态管理

3. 替代方案对比

方案 适用场景 优点 缺点
mitt 事件总线 跨组件简单通信 轻量、灵活 不易追踪数据流
Provide/Inject 祖先-后代通信 Vue 原生支持 不适合跨分支组件
Pinia/Vuex 全局状态管理 可预测、可调试 相对较重
Props/Emit 父子组件通信 明确的数据流 无法跨层级

五、完整示例

// eventBus.js
import mitt from 'mitt';

const emitter = mitt();

// 可选:添加类型提示(TypeScript)
// type Events = {
//   'user-login': { id: number; name: string };
//   'notification': string;
// };

export default emitter;

// ComponentA.vue
import emitter from './eventBus';

emitter.emit('user-login', { id: 1, name: 'John' });

// ComponentB.vue
import { onMounted, onUnmounted } from 'vue';
import emitter from './eventBus';

onMounted(() => {
  emitter.on('user-login', (user) => {
    console.log('用户登录:', user);
  });
});

总结

Vue 3 中推荐使用 mitt 作为事件总线解决方案,它小巧(200B)且功能完整。但对于复杂应用,建议优先考虑 Pinia 进行状态管理,事件总线更适合作为辅助通信手段。记住要及时清理事件监听,并合理规划事件命名空间,避免事件冲突和维护困难。

相关推荐