Vue组件通信方式

122 阅读1分钟

Vue是一个MVVM框架,组件化开发是Vue中的一个重要概念,组件间的通信显得尤其重要,那Vue中组件通信有哪几种呢?

一、props$emit

父组件通过props向子组件传递数据,在子组件中通过$emit向父组件抛事件,父组件接受到事件之后进行额外处理。

例子如下:点击子组件得按钮后,更改父组件文本颜色。

父组件 parent.vue:

<template>
    <div>
        <div class="text" :style="{color: textColor}">我是父组件</div>
        <child :msg="msg" @changeTextColor="getTextColor"></child>
    </div>
</template>

<script>
import child from "./child";
export default {
    name: 'Parent',
    data() {
        return {
            msg: '',
            textColor: ''
        };
    },
    components: {
        child
    },
    methods: {
        getTextColor(color) {
            this.textColor = color;
        }
    }
}
</script>


子组件child.vue:

<template>
    <div>
        <div>我是子组件</div>
        <div>{{ msg }}</div>
        <button @click="handleClick">点我有惊喜哟</button>
    </div>
</template>

<script>
export default {
    name: 'Child',
    props: {
        msg: {
            type: String,
            default: ''
        }
    },
    data() {
        return {};
    },
    methods: {
        handleClick() {
            this.$emit('changeTextColor', 'red');
        }
    }
}
</script>

image.png

注意:props是单项数据流,只能从父组件传递给子组件,并且在子组件中不能更改props得值,会报错。

二、$children 、 $parent

在父组件中通过this.$children获取子组件实例,this.$children值一个数组,通过索引获取到具体子组件实例进而可以访问到子组件上得属性和方法;在子组件中通过this.$parent访问父组件实例,访问父组件的属性。

示例如下:

父组件parent.vue:

template>
    <div>
        <div class="text" :style="{color: textColor}">我是父组件</div>
        <child @changeTextColor="getTextColor"></child>
        <button @click="changeText">点我改变子组件文本</button>
    </div>
</template>

<script>
import child from "./child";
export default {
    name: 'Parent',
    data() {
        return {
            msg: 'hello world',
            textColor: ''
        } 
    },
    components: {
        child
    },
    methods: {
        changeText() {
            this.$children[0].value = '父组件改变了我得值';
        }
    }
}
</script>

子组件child.vue:

<template>
    <div>
        <div>我是子组件</div>
        <div>父组件得值:{{ $parent.msg }}</div>
        <div>我是子组件得值:{{value}}</div>
    </div>
</template>

<script>
export default {
    name: 'Child',
    data() {
        return {
            value: '测试子组件'
        }
    },
    methods: {
        handleClick() {
            this.$emit('changeTextColor', 'red');
        }
    }
}
</script>
注意点:vue3中移除了对$children的支持

三、provideinject

provide与inject需要一起使用,主要用于高阶插件或组件库时使用,允许祖先节点向子孙节点注入依赖,不论组件嵌套层级有多深。在祖先组件中使用provide,provide 选项可以是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property,子孙节点中使用inject注入,inject可以是一个字符串数组或一个对象,。

示例如下:祖先节点

<template>
    <div>
        <div>我是父组件</div>
        <child/>
    </div>
</template>

<script>
import child from "./child";
export default {
    name: 'Parent',
    data() {
        return {
            msg: 'hello world'
        } 
    },
    provide: {
        foo: 'hello world'
    },
    components: {
        child
    }
}
</script>

子孙后代节点:

<template>
    <div>
        <div>我是子孙节点</div>
        <div>祖先节点传递给我得值:{{foo}}</div>
    </div>
</template>

<script>
export default {
    name: 'GrandSon',
    inject: ['foo']
}
</script>
适用于多个组件嵌套,层级较复杂场景

四、eventBus事件总线机制

eventBus可在任意两个组件间进行通信,发布订阅模式,全局定义一个类去使用, 使用方法如下:

// 可新建一个bus.js文件,然后按需引入该文件
import Vue from "vue";
export default new Vue();

// 在组件中使用
import Bus from "./bus.js";

// 监听事件
Bus.$on(key, fn);

// 触发事件
Bus.$emit(key, 参数列表)

也可自己实现EventBus,EventBus简易实现:

class EventBus {
    constructor() {
        this.clientList = {};
    }
    $on(key, fn) {
        if(!this.clientList[key]) {
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);
    }
    $emit() {
        let key = [].shift.call(arguments);
        if(!this.clientList[key]) {
            return false;
        }
        let fnArr = this.clientList[key];
        fnArr.forEach(fn => {
            fn.apply(arguments);
        });
    }
    $off(key, fn) {
        if(!this.clientList[key]) {
            return false;
        }
        this.clientList[key].forEach((item, index) => {
            if(item === fn) {
                this.clientList[key].splice(index, 1);
            }
        });
    }
    $once(key, fn) {
        const onceFn = (...args) => {
            this.$off(key, onceFn);
            fn.apply(args);
        }
        this.on(key, onceFn);
    }
}
export default EventBus;

五、Vuex

Vuex是一个状态管理器,集中管理所有组件的状态,适用于大型应用项目。

image.png 在vue组件中分发actions,通过action去显式提交mutation,在mutation中改变state状态,进而触发组件渲染更新。

六、ref

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

<!-- `vm.$refs.p` will be the DOM node --> 
<p ref="p">hello</p> 

<!-- `vm.$refs.child` will be the child component instance --> 
<child-component ref="child"></child-component>
// 访问子组件属性或方法
this.$refs.child.属性名/方法名

七、$attrs$listeners

$attrs包含了父作用域中不作为 prop 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件,适用于高阶组件。

$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用.

示例如下:

parent.vue

<template>
    <div>
        <div>我是父组件</div>
        <child :msg="msg" name="hxc" age="18"/>
    </div>
</template>

<script>
import child from "./child";
export default {
    name: 'Parent',
    data() {
        return {
            msg: 'hello world'
        } 
    },
    components: {
        child
    }
}
</script>

child.vue

<template>
    <div>
        <div>我是子组件</div>
        <div>姓名:{{$attrs.name}}</div>
        <div>年龄:{{$attrs.age}}</div>
        <grand-son v-bind="$attrs" :text="text"></grand-son>
    </div>
</template>

<script>
import grandSon from "./grandSon";
export default {
    name: 'Child',
    data() {
        return {
            text: '子组件传递给孙子节点的文本'
        }
    },
    components: {
        grandSon
    },
    methods: {
        handleClick() {
            this.$emit('changeTextColor', 'red');
        }
    }
}
</script>

grandSon.vue

<template>
    <div>
        <div>我是子孙节点</div>
        <div>子节点通过props传递{{text}}</div>
        <div>姓名:{{$attrs.name}}</div>
    </div>
</template>

<script>
export default {
    name: 'GrandSon',
    props: {
        text: {
            type: String,
            default: ''
        }
    }
}
</script>

image.png