在开始讨论 Vue 的事件系统之前,必须明确一件事情:__在 Vue 中,事件分为 DOM 事件和非 DOM 事件, Vue 并没有封装 DOM 事件,但 Vue 提供了指令方便用户绑定 DOM 事件,同时指令也支持绑定非 DOM 事件。__这个指令就是 v-on
指令。
如果想绑定 DOM 事件,只需要 v-on
指令加上事件名称即可,具体使用方法参见官方文档。当然,既然是 DOM 事件,就必须绑定到 DOM 元素上,这一点很关键;如果是绑定非 DOM 事件,就必须绑定到组件上。
非 DOM 事件,我们称之为自定义事件,是 Vue 实现的“event-bus(事件总线)”系统维护的。一个 event-bus 必须提供添加事件监听、触发事件、移除事件监听等功能。在 Vue 中,对应的方法都有提供:
vm.$on
vm.$emit
vm.$off
超简单。所有的事件系统都是遵循“发布/订阅模式”的,首先创建一个列表维护订阅者,这个列表挂载到 vm._events
,又为了区分事件类型,所以分别让各个类型的事件各自维护一个订阅者列表,挂载到 vm._events[type]
,这里的 type 表示事件类型。
至此,事件系统基本搭建完成了,就这么简单。
添加事件监听
就是把订阅者(事件回调函数)添加到对应的事件类型的订阅者列表中,例如:vm.$on('a', function b() {})
就是把 b
这个回调添加到 vm._events['a']
的列表(队尾)中。
触发事件
就是根据给定的事件类型,遍历一次其对应的订阅者列表,然后逐个执行订阅者回调函数。
移除事件监听
就是根据给定的事件类型,遍历一次其对应的订阅者列表,找到对应的回调并将其移出列表。
以上解析只是关键步骤,实际上 Vue 在具体实现中会做一些其它的处理。另外,像 .$once
这种 API 的实现也是很简单的,原理就是通过 .$on
添加监听,对回调进行封装,使其在调用之前立即 .$off
掉事件监听,再执行真正的回调。
事件总线是不需要这两个 API 的,所以在 Vue 2.x 版本中这两个 API 已经去掉了。为什么在 1.x 版本中存在呢?这两个 API 的存在(应该)是为了解决组件间通信的问题。在官方文档中也有说明,这两个 API 在父子组件通信中使用。
如果是相邻组件间通信呢?需要这两个 API 结合起来用。具体怎么用我就不说明了,毕竟__这种用法已经不推荐__了。只能说,我在这种用法中才过很多坑。
说回到实现上,这两个 API 的实现也不难:事件本身是没有父子概念的,但是组件有,所以可以通过组件树的父子关系链对事件进行“广播”(从父向子逐层调用 .$emit
)或者“冒泡”(从子向父逐层调用 .$emit
)。
具体到细节上, Vue 做了一些优化,例如,并不是每次 .$broadcast
都会向每个子节点逐层触发,而是一旦发现该事件类型在某个组件子树并没有绑定,那么整棵子树都不会传播下去。
总而言之,$dispatch
和 $broadcast
这两个 API 还是尽可能少使用比较好。如果你需要一种方法实现组件间通信,不妨尝试只使用 event-bus 的方法(用一个空的 Vue 实例做一个全局的事件总线)。或者如果你只是想关联组件间的状态变化,一个更好的选择是 Vuex 或者 Redux