Skip to Content
个人见解事件总线

为什么在 React 中使用事件总线?

  1. 跨层级通信的便捷 在 React 中,想要让非父子关系的组件间通信,常见的做法是利用 Context 或 Redux 等全局状态管理工具。但这些工具有时会显得“太重量级”或“过度”,而事件总线只需要几个简单的 API(onemitoff)就能实现组件间的轻量级解耦通信。

  2. 避免多层级的“状态上提”或“props 透传”

    • 某些场景只是一对组件(可能相隔多个层级)需要“通知触发”一下,没必要把数据都放在全局 Store,或创建一个专门的 Context。
    • 事件总线能快速解决这类“一对多”或“多对多”传播问题,减少组件间耦合
  3. 对 Redux/Context 的有力补充 就算项目里已经在用 Redux 或 Context,一些简单的消息或操作不一定非要写入全局状态。

    • 比如:发送一个“toast 提示”事件,弹窗消息的展示等,这些并不影响应用核心数据,却需要在任何地方被触发、响应。
    • 此时用事件总线会更简洁,也更符合**“事件-响应”**的直观逻辑。
  4. 更灵活的“订阅-发布”模型 事件总线是典型的 Pub/Sub 模式,组件只要订阅自己关心的事件即可,不必关心事件来源。组件之间不直接耦合,也不要求统一的数据结构;在某些快速迭代的场景中,用起来非常顺手。

我的事件总线有什么特点?

  1. 自动消除多余监听

    • useOn Hook 中,你已经通过 useEffect 返回清理函数做了“解绑”。当组件被卸载时,对应的事件监听会自动 off,从而不会积累大量无用监听
    • 另外,如果给同一个事件重复绑定多次,再卸载时会清理对应的回调引用,不会一直残留。
  2. 支持事件重放(Replay)

    • 当事件在没有任何监听者的情况下被 emit 时,会先被暂存在 _prevEvents 中;当某个监听者随后 on 这个事件时,会立即获得之前 emit 的参数,并在调用完成后从缓存中删除。
    • 这意味着组件即使在挂载稍晚,依然能拿到之前错过的事件。对某些业务场景(如登录后立即获取一些初始化信息、异步加载后才注册事件等)非常有用。
  3. 轻量,不依赖外部库

    • 你的 Emitter 类只有少量核心方法(emitonoffoffAll 等),API 非常简洁,没有额外的状态或插件依赖。
    • 不会额外消耗过多内存或性能,实现也相对直观、可读。
  4. React Hooks 友好

    • 通过 useEmituseOn 两个自定义 Hook,就能在任何函数组件里便捷地使用事件总线;
    • useCallback 确保事件监听函数的引用稳定,避免频繁的解绑-绑定,也减少了重复渲染带来的开销。

“事件总线”真的过时了吗?

  • 很多人之所以觉得“事件总线过时”,往往是因为在工程化的 React 项目里,我们有了 Redux、Context、Recoil 等更强大的全局状态管理方案。然而,这些方案并不会完全替代事件通信的需求。
  • 当你不想将所有逻辑都放入共享状态,或“纯粹只需要一个‘多对多’的事件触发”时,事件总线依然提供了一种轻便直观的解决方式。
  • 比起在 Redux 中写一堆 Action、Reducer,或者在 Context 中创建许多 Provider,有时写几行 emiton 就能解决问题,也可以让工程代码更简单。

适用场景

  1. UI 通知/弹窗/提示:例如全局提示系统、消息通知中心、模态框的打开/关闭等。
  2. 分模块通信:各模块逻辑相对独立,只需在极少情况下互通消息时,不必牵扯到全局状态或繁杂的多层 Props 传递。
  3. 跨页面/跨路由联动:在多页面 SPA 中,某个页面的操作想要影响另一个页面的组件行为,事件总线常常是最简单的做法。
  4. 外部系统触发:某些第三方 SDK 或 WebSocket 数据推送过来时,希望在多个地方都能收到通知;用事件总线可以很容易地实现一对多分发。

结语

  • 虽然“事件总线”看似是一个老话题,但在某些具体业务场景中,依然能发挥它“解耦”“轻量”且“一对多”的独特价值。
  • 支持事件重放、自动清理监听、无 Node 依赖,还能与 React Hooks 天然结合。
  • 与其说事件总线过时,不如说——它只是众多工具之一,恰当地选用它能让开发者在合适的场景下事半功倍

如果你在项目里遇到类似需求,希望这份事件总线及其使用介绍能给你带来一些思路和帮助!

Last updated on