应避免使用 eventbus,因其导致隐式耦合、类型失控、内存泄漏及调试困难;推荐优先采用 props/emits、provide/inject、pinia 或轻量 mitt。

组件通信尽量避免使用 EventBus,核心在于它会悄悄破坏代码的可维护性和可预测性。它不暴露依赖关系,不参与组件生命周期管理,也不受类型系统约束——这些看似“方便”的特性,恰恰是大型项目后期难以调试、重构和协作的根源。
隐式耦合让组件关系变成“黑盒”
一个组件 emit 一个事件,另一个组件在任意位置 on 相同名称的事件,两者之间没有任何声明式关联。父组件不会在 emits 中列出它转发了哪些全局事件,子组件也不会在 props 或 setup 中表明自己依赖哪些总线消息。阅读代码时,你无法通过组件定义快速判断“这个组件到底和谁在交互”。久而久之,事件名重复、监听遗漏、触发无人接收等问题频发。
- 事件名全靠字符串硬编码,拼错或大小写不一致就静默失败
- 组件卸载后若忘记 off,监听函数持续驻留内存,引发泄漏
- 调试时需全局搜索事件名,无法跳转到注册/触发处,链路断裂
更清晰、更可控的替代方案
Vue3 提供了语义明确、类型安全、生命周期友好的原生通信机制,应优先按场景选用:
父子/深层嵌套:用 props + emits + defineEmits
这是最直接、最推荐的方式。数据流自上而下,事件反馈自下而上,IDE 能自动提示、TypeScript 可精准校验。
- 子组件通过 defineEmits 显式声明可抛出的事件及参数类型
- 父组件绑定 @xxx 即完成监听,无需额外注册/解绑
- 响应式 props 修改会自动触发更新,无状态同步延迟
跨层级或兄弟组件:用 provide / inject + reactive
适用于需要被多个后代组件共享且有状态联动的场景(如主题色、用户登录态、表单上下文)。
- 父组件 provide 一个 reactive 对象或包装后的方法,后代组件 inject 使用
- 所有注入值天然响应式,修改即刷新,无需手动触发事件
- 类型支持完整,可配合泛型约束 provide 的 shape
复杂状态或跨模块共享:用 Pinia
当通信本质是“共享状态”而非“传递通知”时,Pinia 是官方推荐的现代化状态管理方案。
- 状态集中定义,变更逻辑封装在 actions,便于复用和测试
- store 可被任意组件 import 使用,无需全局实例或事件桥接
- 内置 Devtools 支持时间旅行、状态快照、依赖图谱,调试效率远超 EventBus
极轻量、临时性通知:用 mitt(仅限必要场景)
如果确实需要松耦合的发布-订阅(例如埋点上报、全局快捷键响应),mitt 是比旧 EventBus 更干净的选择。
- 体积仅 200 字节,无框架耦合,支持 TypeScript 泛型约束事件类型
- 必须在 onUnmounted 中显式 off,否则仍会泄漏
- 建议只用于非业务核心路径,且事件命名加前缀(如 ui:dark-mode-toggle)避免冲突











