问题背景
在 Vue3 中删除了全局事件总线的设计: $on,$off 和 $once 实例方法已被移除,组件实例不再实现事件触发接口。如果向直接通过 Vue 实例触发事件, 并在其他组件进行监听, 在核心 Vue 库里面是无法实现的。
原因:
在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。虽然在短期内往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼。
现有方案
在 官方文档 里面,Vue 建议两个方案(具体的实现代码可以在文档中查看,这里便不再重复):
-
根组件注册:即在根组件中注册监听, 那么在任意的子组件内通过 emit 触发的事件都可以在根组件处得到捕获处理。
-
使用第三方事件总线库, 例如 mitt 或 tiny-emitter。
方案一的问题是处理场景具有局限性, 如果需要处理的内容刚好可在根组件完成,那么这个很好用, 而没有任何额外成本, 但是更多时候希望在某个子组件内完成时, 方案一就无能为力了。
方案二看起来是最佳的替代方案,因为使用第三方库本身就是专注于事件总线本身。它的问题只是会引入新的库, 以及随之而来的学习成本。 如果项目中大量需要事件总线, 那么这个方法毋庸置疑非常合适。 但是如果只有少数几个地方需要, 或者项目对第三方库的引用比较严格, 那么或许可以寻找另一种"平替方案"
其他方案思考:
在官网最后有提到, 使用全局状态管理例如 Pinia 可以实现类似的效果, 但是没有继续展开, 这里结合 Pinia 的文档简单做一个思路的整理和实现思考:
-
监听State
因为 Pinia 的状态可以定义成响应式的, 那么便可以通过监听 State 来实现类似全局事件的效果:-
State 中定义变量 S1
-
通过 Watch 监听 S1 变化,进行相关处理
-
通过调用 Set 方法变更 S1 触发相关的监听方法。
这个方法除了可以是实现零类似全局事件的效果之外, 还有另一个好处,就是 S1 本身便可以作为时间参数进行存储,这样便不需要再额外的定义,大概实现的伪代码如下:
// state.js import { defineStore } from 'pinia' const useEventBusStore = defineStore('eventBus', { state: () => { return { someEventParams: 0 } }, }) // 组件 // 1. 触发 const eventBus = useEventBusStore () eventBus.someEventParams = 1; // 2. 监听 watch(eventBus.someEventParams, (new) => { // 事件处理逻辑 })其实 Pinia 便提供了 State 的监听接口, 即 $subscribe , 道理是类似的, 不过除了订阅 状态, Pinia 还提供了对 Action 的监听,也就是下面要讲的方法 这种方法看起里就更像是触发事件了。
-
-
监听 Action
你可以通过
store.$onAction()来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError允许你在 action 抛出错误或 reject 时执行一个回调函数。以上是 Pinia 对订阅 Action的描述, 这个方法对函数运行追踪很有用。 自然地也可以利用这个特性, 来实现类似事件的效果,
$onAction的定义如下:const unsubscribe = someStore.$onAction( ({ name, // action 名称 store, // store 实例,类似 `someStore` args, // 传递给 action 的参数数组 after, // 在 action 返回或解决后的钩子 onError, // action 抛出或拒绝的钩子 }) => { // 回调函数,实际上在方法调用前执行 }) // 调用unsubscribe可以手动删除监听器 unsubscribe()使用时需要注意的是:
$onAction的回调函数是在方法执行前执行的, 所以如果监听的结果需要再调用结束后完成的话, 监听的逻辑并不应该写在回调函数中,而应该参数中的 after 钩子内实现:const unsubscribe = someStore.$onAction(options => { options.after(result => { // 先监听逻辑 }) })这个方法的好处如下:
第一可以通过方法的规范命名(如 onSomething) 实现更加符合事件使用习惯的代码,增加可读性。
其次,通过方法返回值可以带出事件参数,无需从其他处去取。
另外,可以通过调用
$onACtion的返回值手动移除监听, 使用起来更加灵活。
总结:
在大多数场景中,我们都会使用全局状态组件如Pinia. 但是对事件总线的依赖,或许只是一个或两个方法, 那么此时再引入一个事件总线的库, 也许并不是一个最佳的方案, 就如官网所说, 如果可以在根组件进行处理, 那么直接在 CreateApp 处实现一个监听方法即可, 而如果需要在组件内监听的话, 也可以使用对全局状态的监听或状态组件如Pinia自己提供的一些特性来实现相关功能。在降低项目复杂度的同时,通过合理的代码组织甚至实现效果并不比第三方库差太多。
参考链接: