如何在(Vue/React)项目中的应用 mitt
Published onAugust 03, 2025
-Views
5Minutes Read
是一个非常轻量级的事件发射器(Event Emitter),它的核心思想是提供一个简单的发布/订阅(Pub/Sub)模式,让你可以轻松地在应用程序的不同部分之间进行通信,而无需它们直接相互依赖。
在上一片文章中我介绍了 mitt 的基本概念和用法,这篇文章将介绍在 Vue 和 React 项目中如何使用 mitt。
直接 import 使用
直接 import 使用是最简单最直接的方式,前一篇文章中所有的例子都是这种方式。
第一步,安装 mitt 并创建实例
接下来需要在哪里使用就可以引入emitter 了。
例如监听事件:
触发事件:
这种直接 import 的方式,几乎适合任何项目。
挂载到 Vue 实例
Vue2 版本挂载到实例
在 Vue2 版本的时候比较常用这种方式:
然后在 Vue 组件中通过 来使用它:
这种方式的好处是,不需要在每个组件中都 import mitt,只需要在入口文件中创建实例,然后在组件中通过 来使用它。
Vue3 版本挂载到实例
Vue3 版本中有些不太一样了,需要使用 方法来创建应用实例,然后通过 来挂载实例。
然后在任何组件中都可以通过 来使用它,这一点和 Vue2 版本是一样的。
不过这需要使用 Options API 的方式,如果你使用的是 Composition API,可以这样写:
可以看到我们用到了 方法来获取当前组件实例,然后通过 来访问全局属性。
虽然它能解决问题,但 Vue 官方不推荐经常使用 ,甚至现在已经将它从文档中删除掉了。
个人感觉这个写法比较复杂,不如直接 import 方式简单易用。
因此相对而言更推荐使用 import 的方式或者下面的 povide/inject 的方式。
使用 共享实例(推荐)
除了挂载到全局属性上,在 Vue 3 中,更推荐使用 来精确地控制 实例的作用域,这能更好地实现解耦。
你可以在入口文件中直接对 app 提供 实例:
你也可以在任意一级组件开始提供 实例,然后在子组件中通过 来接收它。
在子组件中,通过 来接收 。
为什么我更推荐 provide / inject
对于简单的项目,直接 看起来更快更方便。但从工程实践的角度来看,我强烈推荐使用 。这不仅仅是个人偏好,更是为了解耦、可维护性和可测试性。
下面,我将深入探讨为什么 是更优的选择,以及它背后的设计思想。
直接 :全局单例的便利与风险
当你在一个文件中创建 实例并将其 ,然后在其他组件中直接 时,你实际上是在使用全局单例模式。
这种做法简单直接,但它存在几个核心问题:
-
强耦合:你的组件与 文件的路径和具体实现产生了硬编码依赖。如果你想更换事件总线库,或者将 分割成多个实例,你必须修改所有 过它的组件。这就像公司里的所有员工都只知道一个特定电话号码,一旦号码更换,所有人都得去更新通讯录。
-
可测试性差:在单元测试中,所有组件都共享同一个全局 实例。这意味着一个测试用例发出的事件可能会影响到另一个测试用例,导致测试结果不确定。为了确保测试的纯粹性,你需要在每个测试用例之后手动清理 的状态,这增加了测试的复杂性。
-
缺乏作用域控制:这种全局单例模式使得任何组件都可以订阅和发布任何事件。虽然可以通过命名规范来缓解,但它无法从代码层面强制隔离。这增加了事件冲突和意外副作用的风险。
为什么说 更好?
先说说 和 的核心作用
和 模式就是 Vue 的“依赖注入”机制,它主要用来解决跨层级组件通信的问题。
- :提供者(Provider)。它是一个发布动作。在父组件中,你使用 来注册一个依赖(比如 实例),并给它起一个唯一的键名(比如 )。它向下传递,但不会向上或向兄弟组件传递。
- :消费者(Consumer)。它是一个获取动作。在任何子孙组件中,你都可以使用 来获取父级提供的依赖。它会沿着组件树向上查找,直到找到第一个匹配的 。
这种模式有巨大的架构优势:
- 弱耦合:组件只通过一个字符串键名(如 )来请求依赖,它完全不关心这个依赖是如何创建的,也不关心它来自哪个文件。这让你的组件与具体的实现解耦,使它们更加独立和可复用。
- 可测试性强:在单元测试中,你可以轻松地**模拟(mock)**依赖。你只需 一个假的 对象,就可以完全控制事件的发送和接收,而不会影响到其他测试。这大大简化了测试环境的搭建。
- 灵活的作用域控制: 允许你在组件树的任意层级提供 实例,从而实现作用域化的事件总线。你可以为用户模块提供一个 ,为购物车模块提供一个 ,将事件通信限定在各自的子树内,从根本上杜绝了模块间的意外耦合。
现在我们来详细对比为什么这种模式在很多场景下比直接 更有优势。
特性 | 方式 | 方式 |
---|---|---|
依赖关系 | 强耦合:组件直接依赖于 文件的路径和具体实现。 | 弱耦合:组件只依赖于一个键名(),它不关心具体实现和文件位置。 |
作用域 | 全局单例:整个应用只有一个 实例。如果一个事件被触发,所有监听者都会收到,这可能导致意外的副作用。 | 可控制作用域:你可以在组件树的任意层级提供 ,形成局部作用域。一个父组件提供的 只对它的子孙组件可见,避免了全局污染。 |
可测试性 | 难以隔离:所有测试都共用同一个 实例,测试之间可能互相影响。需要手动清理状态。 | 易于模拟:在测试时,可以轻松地 一个模拟的 ,从而完全控制测试环境,实现更纯粹的单元测试。 |
灵活性 | 僵化:要更换事件总线库,你需要修改所有 的文件。 | 灵活:提供者可以随时更换 实例的实现(比如换成一个功能更强大的事件总线库),而子孙组件的代码无需改动。 |
结论:从全局依赖到局部控制
在 Vue 中, 模式将组件从“如何获取依赖”的困境中解放出来,让组件能够更专注于自身的业务逻辑。这是一种**控制反转(Inversion of Control)**的设计思想,它将依赖的创建和提供交给更高层级的组件来处理。
- 如果你的项目很小,或者你只是想快速实现一个功能,直接 确实可以。
- 但如果你的项目正在成长,或者你希望构建一个可维护、可扩展、易于测试的系统,那么使用 来提供 实例无疑是更成熟、更专业的选择。
多花几行代码,换来的是更高的解耦度、更强的可测试性,以及在项目长期发展中至关重要的代码可维护性。
React 示例:使用 Context API 共享实例
和 Vue 项目相似,除了直接 import 方式,React 项目中也可以使用 Context API 来共享 实例。
React 的 Context API 和 Vue 的 provide / inject 是功能和机制上非常相似的两种模式
首先,创建一个 。
然后,在父组件中通过 传入 实例。
最后,在子组件中通过 钩子来使用 。
理由和上一节一样,在 React 中,Context API 是共享 实例的最佳实践。
总结
本文介绍了 mitt 在 Vue 和 React 项目中的应用,并介绍了 模式的优势。
- 方式适合中小型项目,但不适合大型项目。
- 或者 方式适合大型项目,它提供了更好的可测试性、更灵活的作用域控制、更好的架构设计。
因此具体使用哪种方案要根据项目规模和 的使用场景来决定。
Tags:
#Mitt