最近在用element-ui重构前端项目,无意之中翻阅到一个比较好用的组件间通信方式,借助于vue的封装的发布-订阅消息模式与mixin语法。在开始之前先总结下vue常用的组件间通信方式,具体如下:
1、props与自定义事件
优点:常用的父子、子父组件传递方式,简单易懂
缺点:子父、父子之间传参比较高效,但是爷孙,兄弟组件之间存在通信短板,只能一级级传递
2、vue 2.4中新增的$attrs与$listeners
优点:解决了组件嵌套层次较深问题,通过在组件中绑定组件的属性值与监听组件的事件监听对象,子组件可以轻松访问到上层作用域的事件回调方法
缺点:相对于props与自定义事件,只是简化了写法,本质上属性也是一级级传递的,如果组件的层级是A>B>C,C组件如果想要访问到A组件的事件回调,那么在B组件中需要绑定A组件的属性attrs与事件对象listeners。
3、借助于发布-订阅模式
优点:发布-订阅设计模式所擅长点就是解决模块间通信,无论何种方式的通信方式,理论上都可以解决,在vue中,大量使用了该设计模式,只需要实例化一个vue实例,作为一个单独的事件中心引入即可。发布订阅模式具体内容可参考:https://www.cnblogs.com/gerry2019/p/10241488.html
缺点:发布-订阅模式只要有订阅消息,在分发事件的时候,都会触发订阅的回调,所以在开发过程中要注意自定义事件的命名空间,防止一些不必要的触发操作。
4、借助vuex管理状态
优点:引入状态管理,可以统一的管理项目状态,针对大型复杂项目适用性较好
缺点:需要额外维护一个vuex对象,中小型项目不适合
5、vue 2.2中新增的provide/inject
优点:可以从父组件注入需要共享给子元素的数据,如果注入的的数据是响应式,那么组件之间可以实现双向数据通信
缺点:官方并不推荐在业务代码中使用此模式,因为这种双向通信可能会导致组件的状态发生混乱
以上为vue中常用的几种组件间通信方式,以下为element-ui中采用的组件通信方式,本质上也是发布-订阅模式,与上述方法3大致相似,只是引入了自定义事件的生效的作用域,可以规避一些意料之外的事件触发。因为稍微不注意消息的命名空间,就可能会导致一些意外之外的错误,除此之外,当两个模块功能相似,如果单独都写一遍,可能存在大量冗余代码,在vue中我们想到的策略可能是借助于mixin,抽离公共的业务代码,但是假如抽离出了公共的消息订阅内容,那么此时问题就来了,如果我只想A模块内容发生改变,B模块不变,这个就会存在策略性问题,element-ui中的通信策略可以很好的规避掉以上两种坑,具体实现逻辑如下:
/** * * @param {目标组件名称} componentName * @param {事件名称} eventName * @param {载荷参数} params */ 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])); } }); } export default { methods: { /** * * @param {目标组件名称} componentName * @param {事件名称} eventName * @param {载荷参数} params */ 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)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } } };
从以上代码可以看出,在组件通信过程中,需要额外加一个componetName属性,其主要作用就是对分发的事件加入一个命名空间,命名空间内部的事件才会触发,否则会直接跳过,增加了业务代码的容错能力。在使用的过程中,只需要在需要引用的地方引入这个混入,就可以实现组件间双向通信。element-ui中的具体使用如下:
mixin语法可参考:https://cn.vuejs.org/v2/guide/mixins.html
mixin本质上就类似于继承,有助于简化业务代码
vue 2.x中部分新增组件通信方式可参阅vue API文档:https://cn.vuejs.org/v2/api/#provide-inject