译:Vue.js——为什么事件总线(event bus)是一种不好的实践
在研究Vue组件通信的过程中,了解到事件总线(event bus)的概念,直觉上不太认同这种方式。
找到了一篇文章,正是我所想,译文佐证。
2019年2月,我写了一篇关于Vue.js全局事件总线的文章。
事件总线这种模式,应用于一种很常见的场景:组件间传递数据,不止是父传子,还有子传父(译注:兄弟组件传值也适用)。
事件总线还能从父组件触发子组件的事件、调用子组件的方法。
然而,由于很多原因,它并不是一种好的模式,如果我们有其他选项,应该尽量避免使用事件总线。
以我多年的经验来说:事件总线是一种反模式
,不应该使用它。为什么?
在这篇文章中,我会试着用最简单的方式来解释。当然,欢迎大家在评论区讨论、写下你的想法和实践。
作为回顾,我们看一下事件总线如何使用,以及它是怎么运行的:在一个单独的文件中创建新的Vue实例,导出这个Vue实例,然后,在任意组件中导入,使用$emit
和$on
方法来声明事件。以下是一个非常简单的例子:
1 | import Vue from 'vue' |
1 | import {eventBus} from '@/global/eventBus' |
1 | import {eventBus} from '@/global/eventBus' |
这是最简单的例子了,它运行很正常:创建了事件总线(新的Vue实例),在组件中导入总线,然后设置触发事件。简单、清晰、易读。那么,事件总线有什么不好吗?
事件总线的问题
首先,命名问题。一个事件总线实例不区分任何命名空间,因此有可能会出现事件重名的问题。
当然,可以创建多个事件总线实例,比如同一类组件创建一个。但当应用规模增大时,会有更大的问题。
也可以定义一套命名规则,例如:myComponent/myEvent
或mycomponent_myevent
等等。这当然是可行的,但我们在编码时必须记住这个规则,我们的整个团队必须记住这个规则。这还不是我不喜欢事件总线最主要的原因。
最大的问题是,事件的定义$on
,我们用它来创建事件,但还有$off
呢!我们还必须记住它,在组件beforeDestory
生命周期销毁事件。
为什么?如果不销毁,事件绑定的方法仍然会被触发,即便组件被销毁了。
因为我们用的是全局的事件总线,它仍然在内存中运行着。所以我们必须一直记得销毁事件——当应用规模扩展时,就会造成非常严重的问题,最终难以调试和维护。
它在实际使用中会造成什么问题?按我的经验来描述:有一个父组件List,展示许多子组件。点击子组件,跳转到子组件详情,点击返回,返回父组件。父子都使用事件总线来双向传递数据、事件。同时,子组件还有孙组件,孙组件中的方法也会触发全局事件——传递给List(也就是根节点)。问题来了:如果我们忘了用$off
,跳转到了一个子组件,回到父组件,跳转到第二个子组件,再回到父组件,跳转到第三个子组件,然后触发全局事件…List接收到的事件不是一次,而是三次传递了不同数据的事件!这样是有问题的。
用Vuex替代事件总线
当然,如果一直记得用$off也可以,但这并非可控的做法,非常容易漏掉。
我推荐的最佳选项是用Vuex替代事件总线。
它提供了命名空间,重名不再是问题。
它的状态清晰、明确,我们很清楚系统发生了什么。
它提供了dispatch、action和commit,很容易了解数据的变化。
如果我们移除或销毁了组件,Vuex中的变化不会再触发任何方法:有一个例外,就是Vuex内部的push函数,你必须直接使用它。
总之,Vuex清晰、简洁,给你更好的控制手段。