前言
在最近的面试中曾经两次被问到了Element-UI的通讯机制。老实说,我确实没有了解过Element-UI的源码,而且在第一次被问到时也不以为意。直到第二次被问到我才意识到,这难道也是一个热门考点?所以今天专门来看看Element-UI究竟用了什么特别的通讯机制。
Element-UI的通讯
上源码:
function broadcast(componentName, eventName, params) {
//递归子组件,查找命名空间内组件
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
//分发子组件内订阅消息
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
function dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
//循环查询父组件,找到目标父组件
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
//分发父组件订阅内容
parent.$emit.apply(parent, [eventName].concat(params));
}
},
好处
我们可以看到Element-UI专门封装了broadcast/dispatch2个方法,通过向上或者向下来触发组件事件。这样的设计当然有好处:
- 可以隔代子组件调用方法。在一般的父传子通讯中,信息必须一代一代往下传。如A-B-C,如果想要从A向C通讯,就必须要B的配合。而broadcast方法可以实现对B的零侵入。
- 不难发现这样的实现其实本质上就是发布订阅,而发布订阅中最容易遇到的问题就是对命名空间的管理。而这种方法直接用组件名作为名命空间,在开发上可以降低对名命空间的管理压力。
缺陷
当然这个方案也有解决不了的问题:
- 无法同时触发子代和孙代的事件。可以看到2个方法都是执行了最近的一代组件的事件之后就停止了。如果想要在触发子代事件之后,也触发孙代的事件,就必须修改方法的停止边际。但这样又会导致一个新的问题,就是会让事件的触发链变长,出现无效遍历。
- 无法触发兄弟组件的事件。无论是broadcast还是dispatch,都只能向上/下触发事件,并不能触发兄弟间的事件。
VUE中也有broadcast/dispatch?
然而broadcast/dispatch并不是Element-UI的原创。早在VUE 1.x版本中已经有了broadcast/dispatch,只是在2.x之后就被弃用了。让我们来看看当时VUE的实现
Vue.prototype.$dispatch = function (event) {
var shouldPropagate = this.$emit.apply(this, arguments)
if (!shouldPropagate) return
var parent = this.$parent
var args = toArray(arguments)
args[0] = { name: event, source: this }
while (parent) {
shouldPropagate = parent.$emit.apply(parent, args)
parent = shouldPropagate ? parent.$parent : null
}
return this
}
Vue.prototype.$broadcast = function (event) {
var isSource = typeof event === 'string'
event = isSource ? event : event.name
if (!this._eventsCount[event]) return
var children = this.$children
var args = toArray(arguments)
if (isSource) {
args[0] = { name: event, source: this }
}
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i]
var shouldPropagate = child.$emit.apply(child, args)
if (shouldPropagate) {
child.$broadcast.apply(child, args)
}
}
return this
}
观察代码可以发现,VUE的broadcast/dispatch设计思路跟Element-UI的还是很不一样的 。VUE中的这2个方法,目的是向上/下派发某个事件,而且会在第一次接收到后停止冒泡,除非返回 true。
两者差异
其实严格来说,这两者之间是无法对比的。因为他们的设计目的并不一样,Element-UI中是向上/下触发某个组件的某个事件。而VUE是顺着组件链向上/下派发某个方法。
VUE为什么要弃用broadcast/dispatch?
我好奇的是VUE为什么要弃用broadcast/dispatch?这在VUE 2.x的官网文档中可以找到答案
文档地址:cn.vuejs.org/v2/guide/mi…
显然在1.x的时候,VUE的跨代通讯手段并不完整。诸如VUEX,EVENT BUGS这种方法还没有推广。当2.x之后官网更希望开发者可以用更加合理的手段。
我们是否应该用broadcast/dispatch?
既然Element-UI都在用,那我们在业务中是否应该也可以考虑用broadcast/dispatch呢?我个人认为在大多数的情况下broadcast/dispatch都不是我们最好的选择。
不得不承认在Element-UI作为一个组件库,使用broadcast/dispatch是一个不错的选择,因为组件库的设计往往是规范的,开发者可以在组件之间制定很多的条件束缚来减少边界情况。
而在业务项目中需求是不固定的,我们的代码会被经常修改,这意味着组件间严密的规范不太可能会被严格遵循。使用broadcast/dispatch很可能会出现命名空间混乱,额外组件事件被触发,代码冗余等问题。这时候VUEX,EVENT BUS显然是更好的选择。
总结
Element-UI的broadcast/dispatch是一个在某些场景下的有效设计,如果你正好在开发组件库,或者一些紧密的通用组件,broadcast/dispatc可能可以为你提供一种思路。但对于业务开发来说broadcast/dispatc很多时候并不是最好的方案。