vue组件通信方式

2018年11月27日Web前端

模块化,组件化在前端框架中已经是很常见的玩意了,所以了解组件、模块之间的通信那就很重要了。 组件通信方式的话,有父与子,兄弟之间,祖先与子等方式。这儿主要以父子关系取阐述这些通信机制。

一、props方式

父--->子

父组件传props给子组件

<son :what="name" :hello="world" :callback="thing"></son>

设置了传递的值的内容,name为一个对象,hello为字符串。

data() {
    return {
        name: {
            a: 1,
            b: 2
        },
        world: '123',
        thing: function() {
            console.log(arguments)
        }
    }
}

子组件中

props: {
    what: {
        type: Object,
        default: {}
    },
    hello: {
        type: String,
        default: '456'
    },
    callback: {
        type: Function,
        default: function() {}
    }
}

props类似于传参的方式传给子组件,子组件中就可直接使用this去访问和使用。

子--->父

直接修改引用类型的数据。

前面例子中,father组件传给了son组件一个对象,son组件内可以直接修改传入的对象,由于引用关系,father内的组件值也会改变,此时我们只需要在father设置一个监听就行了。

// 监听传给子组件的值
watch: {
    name: {
        handler(nV, oV) {
            console.log(nV);
        },
        deep: true
    }
}

father组件就能实时获取son组件对传入值操作的情况了

传入函数方式

前面的例子中,father组件向son组件传了thing这个方法,而在son组件中可以通过调用这个方法向father组件传递数据。

mounted() {
    // 定时模拟用户操作了啥后
    setTimeout(() => {
        this.callback(1, 2, 3);
    }, 2000);
}

而在father组件中定义callback的地方就可以拿到这几个参数啦。

注意:我们直接修改father传给son的非引用类型的值时,是不成功的,而且vue会提示错误。

二、$attrs与$listeners

假如有父、子、孙三层组件,父要向孙通过props的方式传递数据,按照第一种方式的话得父调子时写一层,子调孙时再写一层,很繁琐且读起来很不便捷。 所以vue在2.4版本中增加了$attrs和$listeners来处理这个问题。$attrs使得属性能够穿过中间组件,而$listeners是处理绑定事件的。 父组件中,在先前的例子上加了个函数,data的内容不去改变。

<son :what="name" :hello="world" :callback="thing" @show="showMe"></son>
methods: {
    showMe(a) {
        console.log('I am father', a);
    }
}

在son组件中,只使用hello这个变量值,其他的数据都要给grandchild组件。

<grandchild v-bind="$attrs" v-on="$listeners"></grandchild>
export default {
    components: {
        grandchild
    },
    props: ['hello']
};

son组件中只接收了hello,剩余的属性将通过$attrs传递给了grandchild组件。而$listeners也将绑定在son上的事件传了下去。在grandchild中,我们打印通过props传过来的属性的内容,并emit祖父级组件上的事件,

export default {
    created () {
        console.log(this.$attrs);
        this.$emit('show', 'yes');
    }
};

此时控制台的打印:

注:inheritAttrs配合使用,表示组件是否把未被注册的props呈现为普通的HTML属性。

三、$on与$emit

该方式,严格说只有子组件向父组件通信。 父组件中:

<son @what="show"></son>
methods: {
    show() {
        console.log(arguments);
    }
}

子组件中通过$emit去调用父组件中的方法,

mounted() {
    // 定时模拟用户操作了啥后
    setTimeout(() => {
        this.$emit('what', 1, 2)
    }, 1000);
}

这种方式是不是比props方式中直接传函数更“和谐”些呢?

四、Bus方式

在外部定义一个总的js文件用于处理组件之间的通信,该方法不止是父子组件之间的通信,只要引用该文件,任何两个组件之间都是可以进行消息传递的(父子,兄弟等等)。

该种通信下,其实没有父子关系,只有监听与触发的关系,为了方便理解,还是使用父与子。 新建一个bus.js文件,编辑该文件为:

import Vue from 'vue';
export default new Vue;

father和son组件中都增加对bus.js文件的引用

import Bus from './bus.js';

father组件在创建之后,使用$on增加事件的监听,

created() {
    Bus.$on('nice', (a, b) => {
        console.log(a, b);
    });
}

son组件在mount后触发对应的nice事件:

mounted() {
    Bus.$emit('nice', {
        name: 111
    }, 123);
}

此时父组件中就能获取到对应的参数了。当然反过来,子组件$on,父组件$emit,就成了父组件向子组件通信的方式了。

五、inject与provide

父组件中使用provide来提供变量,在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provide中的数据。该方式只要在父组件的生命周期内,子组件都可以注入。

父组件中

<son></son>
provide: {
    name: 'demo'
}

子组件中对应的inject该变量就行了,当然为了安全起见,可以给他设定默认值。

inject: {
    name: {
        default: '1234567890'
    }
}
// inject: ['name'] // 简写方式

在element-ui中的form等表单组件时,用的还是比较多的。为了简单,仍然用父子来代替多层级的关系。

六、$parent和$children

vue2中没有了broadcast和dispatch这种通信方式,但是通过对$parent和$children的处理,我们仍可以模拟这种方式,例如在element-ui库中,作为混入的emitter.js文件中,就用此实现了事件的触发机制。

详细的内容可以查看:element-ui emitter.js文件源码学习

七、$ref

被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的$refs对象上。如果在普通的DOM元素上使用,那么指向的就是普通的DOM元素。

通过直接取到对应的组件来获取对应的值。 比如在子组件中简单的变量,

data() {
    return {
        name: 'aaa'
    };
}

在父组件中,只要取到对应的ref就可以获得子组件下所有的属性,

<son ref="son"></son>
mounted() {
    console.log(this.$refs.son.name);
}

控制台上就可以获取到对应的属性了。

八、vuex

vue的状态管理工具,不详细说了,网上有很多的教程,而且我研究的也不深入。

九、本地存储

cookie,storage,indexedDB等一些用于前端本地存储的也是可以用来给组件之间交互的。

十、总结

我们来总结下,非vue的方式有使用本都存储的方式,而以上所有的方式都可以用于父子组件之间的通信。父到孙组件的话,有$attrs和$listeners,bus方式,inject和provide,vuex。

兄弟直接可以使用bus方式和vuex或者先传给父组件,再广播(利用$parent和$children查找也可以)。

bus方式可以在任何关系的组件中使用,不适用vuex的简单项目中可以选取,而项目复杂的可以上vuex进行管理。