最近在用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中的具体使用如下:

  element-ui源码之组件通信那些事-LMLPHP

element-ui源码之组件通信那些事-LMLPHP

  mixin语法可参考:https://cn.vuejs.org/v2/guide/mixins.html

  mixin本质上就类似于继承,有助于简化业务代码

  vue 2.x中部分新增组件通信方式可参阅vue API文档:https://cn.vuejs.org/v2/api/#provide-inject

06-30 05:39