Vue 3 中官方移除 $emit 跨组件通信能力,推荐用 reactive + onBeforeUnmount 实现轻量 EventBus 或使用 mitt 库;也可封装 useEventBus Hook 统一管理监听与清理,避免内存泄漏,而 provide/inject 不适合作为 EventBus。

Vue 3 中官方已移除 $emit 在非父子组件间通信的能力,也不再内置全局 EventBus。但你仍可通过组合式 API + provide/inject 或独立的响应式对象(如 reactive / ref)轻松实现轻量、类型安全、可维护的跨组件事件总线 —— 它比 Vue 2 的 new Vue() 更现代、更契合 Composition API 设计理念。
用 reactive + onBeforeUnmount 实现简易 EventBus
这是最常用、零依赖、易理解的替代方案:创建一个响应式对象管理事件回调,用 onBeforeUnmount 自动清理监听器,避免内存泄漏。
示例(eventBus.js):
1. 创建事件总线实例
立即学习“前端免费学习笔记(深入)”;
import { reactive, onBeforeUnmount } from 'vue'
const eventBus = reactive({
events: {},
$on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
},
$emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(...args))
}
},
$off(event, callback) {
if (!this.events[event]) return
if (!callback) {
delete this.events[event]
} else {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
})
export default eventBus
2. 在组件中使用
- 发送事件:
eventBus.$emit('user-updated', { id: 1, name: 'Alice' }) - 监听事件(记得卸载):
import { onMounted, onBeforeUnmount } from 'vue'
import eventBus from '@/utils/eventBus'
export default {
setup() {
const handleUserUpdate = (user) => console.log('收到用户更新:', user)
onMounted(() => {
eventBus.$on('user-updated', handleUserUpdate)
})
onBeforeUnmount(() => {
eventBus.$off('user-updated', handleUserUpdate)
})
return {}
}
}
用 mitt 实现更专业的跨组件通信
mitt 是一个极简(仅 200B)、无依赖、TypeScript 友好的事件发射器,是 Vue 3 中替代 EventBus 的主流选择。
安装与使用
npm install mitt
创建统一实例(eventBus.js):
import mitt from 'mitt'
const eventBus = mitt()
// 可选:导出类型定义(配合 TS)
export type Events = {
'user-created': { id: number; name: string }
'dialog-closed': string
}
export default eventBus as mitt.Emitter<Events>
在组件中(Composition API):
- 监听:
eventBus.on('user-created', handler) - 触发:
eventBus.emit('user-created', { id: 1, name: 'Bob' }) - 移除监听(推荐在
onBeforeUnmount中):eventBus.off('user-created', handler)
✅ 优势:支持泛型类型提示、自动类型推导、语义清晰、社区成熟、无内存泄漏风险(只要手动 off 或用封装的 hook)。
进阶:封装 useEventBus Hook(推荐)
把事件注册/销毁逻辑封装成可复用的组合式函数,提升一致性与安全性。
composables/useEventBus.js:
import { onBeforeUnmount } from 'vue'
import eventBus from '@/utils/eventBus'
export function useEventBus() {
const listeners = new Map()
const on = (event, callback) => {
eventBus.$on(event, callback)
if (!listeners.has(event)) listeners.set(event, [])
listeners.get(event).push(callback)
}
const off = (event, callback) => {
eventBus.$off(event, callback)
if (listeners.has(event)) {
const list = listeners.get(event)
const index = list.indexOf(callback)
if (index > -1) list.splice(index, 1)
}
}
onBeforeUnmount(() => {
listeners.forEach((callbacks, event) => {
callbacks.forEach(cb => eventBus.$off(event, cb))
})
})
return {
on,
off,
emit: eventBus.$emit
}
}
组件中直接调用:
import { useEventBus } from '@/composables/useEventBus'
export default {
setup() {
const { on, emit } = useEventBus()
on('notification', (msg) => alert(msg))
const send = () => emit('notification', 'Hello from Button!')
return { send }
}
}
为什么不推荐 provide/inject 做 EventBus?
虽然 provide/inject 可以穿透多层组件传递事件总线,但它本质是“依赖注入”,不是事件系统:
- 它无法天然支持一对多广播(需手动管理多个 injecter)
- 缺乏统一的生命周期管理(如自动清理)
- 容易造成隐式依赖,降低可读性
- 不适用于非组件上下文(如工具函数、Pinia action 内)
✅ 正确用法是:用 provide/inject 提供状态或服务,而用 mitt 或自定义 reactive 对象做事件分发 —— 各司其职,更清晰。


















