vue 组件通信的几种方式

113 阅读3分钟

方法一、props、$emit

props、$emit是最常用的父子组件通信方式,父<->子

props:父组件向子组件传值
首先在子组件内定义字段接收从父组件传的值

<template>
    <div class="B">
        <p>B</p>
        <p>{{msg}}</p>
    </div>
</template>
<script>
export default {
    name: "B",
    props: {
        msg: String //在定义字段接收从父组件传的值
    }
}

然后在父组件内使用子组件定义的字段msg向子组件传值

<template>
    <div class="A">
        <p>A</p>
        <B msg="我是A组件传的数据"/>
    </div>
</template>

<script>
import B from './B.vue'
export default {
    components: { B },
    name: 'A',
}
</script>

$emit:子组件向父组件传值

$emit函数解释
子组件通过$emit可以调用父组件的自定义函数,其接受两个参数,$emit(eventName, [...args])
event:事件名
[...args]: 需要传递的数据

在父组件定义事件message,接收子组件的事件调用

<template>
    <div class="A">
        <p>A</p>
        <p>{{msgFromB}}</p>
        <B @message="receive"/>
    </div>
</template>

<script>
import B from './B.vue'
export default {
    components: { B },
    name: 'A',
    data() {
        return {
            msgFromB: "",
        }
    },
    methods: {
        receive(msg){
            this.msgFromB = msg;
        }
    },
}
</script>

子组件通过$emit("message", value)调用父组件message绑定的函数,进行传值

<template>
    <div class="B">
        <p>B</p>
        <button @click="sendMsg">向A组件传值</button>
    </div>
</template>
<script>
export default {
    name: "B",
    props: {
        msg: String
    },
    methods: {
        sendMsg(){
            this.$emit("message", "我是B组件传的值")
        }
    },
}
</script>

方法二、EventBus
EventBus又名事件总线,通过创建空vue实例的方式实现各组件间的通信,该方式可以实现任意组件间的通信 首先创建eventBus.js文件,导出vue实例,代码如下:

import Vue from "vue";
export const eventBus = new Vue();

然后在需要使用消息传递的组件内引入evenBus,通过使用eventBus的$emit来传递消息

<template>
    <div class="B">
        <p>B</p>
        <button @click="sendMsg">通过EventBus传值</button>
    </div>
</template>
<script>
import {EventBus} from '../eventBus.js'//引入EventBus
export default {
    name: "B",
    props: {
        msg: String
    },
    methods: {
        sendMsg(){
            EventBus.$emit("message", "我是EventBus传的值")
        }
    },
}
</script>

在需要接收事件的组件内引入EventBus,使用$on来监听发送的事件

<template>
    <div class="A">
        <p>A</p>
        <p>{{msgFromBus}}</p>
    </div>
</template>

<script>
import {EventBus} from '../eventBus.js'//引入EventBus
export default {
    name: 'A',
    data() {
        return {
            msgFromBus: "",
        }
    },
    mounted() {
        EventBus.$on("message", (msg) => {
            this.msgFromBus = msg;
        })
    },
}
</script>

这样就轻松实现了任意组件间的通信,在使用EventBus的时候需要注意,在页面销毁的时候要使用EventBus.$off("message")移除监听,避免反复监听。

还可以把EventBus配置成全局变量使用,直接在main.js内设置全局变量即可

Vue.prototype.$bus = new Vue();

使用方法

//发送消息
this.$bus.$emit("message", msg)

//监听消息
this.$bus.$on("message", (msg) => {
            
})

//移除监听
this.$bus.$off("message")

方法三、Vuex
直接上 Vuex官网 吧,里面有详细的安装与使用介绍。需要注意的一点是vuex的数据并不会保存到本地,如果页面刷新,数据也会丢失,所以如果有需要,可以配合localStorage使用。

方法四、$attrs/$listeners
这两个对象一般在多级组件嵌套时使用,在vue2.4以上版本支持,下图为vue官网对这两个属性的解释

image.png 举例说明:比如现在有A、B、C三个组件。
组件A:

<template>
    <div class="A">
        <p>A</p>
        <B msgB="BBB" msgC="CCC" @listener="onListener"></B>
    </div>
</template>

<script>
import B from './B.vue'
export default {
    components: {B},
    methods: {
        onListener(){
            console.log("listener事件被调用");
        }
    },
}
</script>

组件B:

<template>
    <div class="B">
        <p>B</p>
        <C v-bind="$attrs" v-on="$listeners"></C>
    </div>
</template>
<script>
import C from './C.vue'
export default {
    components: {C},
    props: {
        msgB: String
    },
    mounted() {
        console.log("组件B $attrs", this.$attrs);
        console.log("组件B $listeners", this.$listeners);
    },
}
</script>

组件C

<template>
    <div class="C">
        <p>C</p>
        <button @click="onClick">回调listener</button>
    </div>
</template>
<script>
export default {
    props: {
        msgC: String
    },
    mounted() {
        console.log("组件C msgC", this.msgC);
    },
    methods: {
        onClick(){
            this.$emit("listener");
        }
    },
}
</script>

然后我们看输出

image.png 可以看到,组件B内输入的$attrs有msgC,也就是上面描述的父作用域中不作为props被识别的attribute绑定(msgB被识别了,所以在$attrs里面没有),而在组件C内调用this.$emit("listener")能被组件A监听到也对应了官方文档$listeners的描述:v-on="$listener"可以把父作用域中的(不含.native修饰器)的所有监听事件传递到内部组件。这两个属性在封装组件时非常的试用。

方法五、provider/inject
先上官方解读 image.png 简单来说就是一个祖先组件通过 provide 提供一个变量,后代所有子孙组件都可以通过 inject 注入这个变量来使用。
举个简单的例子

//祖先组件A提供state
<template>
    <div class="A">
        <B></B>
    </div>
</template>
<script>
import B from './B.vue'
export default {
    components: {B},
    provide: {
        state: 1
    }
}
</script>

//子组件注入state
<template>
    <div class="B">
        <p>B</p>
        <C></C>
    </div>
</template>
<script>
import C from './C.vue'
export default {
    components: {C},
    inject: ["state"],
    mounted() {
        console.log("state=", this.state); //输出state=1
    }
}
</script>

//孙子组件注入state
<template>
    <div class="C">
        <p>C</p>
    </div>
</template>
<script>
export default {
    inject: ["state"],
    mounted() {
        console.log("C组件 state", this.state);//输出state=1
    },
}
</script>

运行代码看输出,可以看到子孙组件都输出了祖先组件提供的state=1的值。 官网provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
下面我们把数据改成可响应的

//祖先组件
<template>
    <div class="A">
        <p>A</p>
        <button @click="onclick">修改provide的值</button>
        <B></B>
    </div>
</template>

<script>
import B from './B.vue'
export default {
    components: {B},
    provide() {
        return {
            obj: this.obj
        }
    },
    data() {
        return {
            obj: {
                state: 1
            }
        }
    },
    methods: {
        onclick(){
            this.obj.state += 1;
        }
    },
}
</script>

//子组件
<template>
    <div class="B">
        <p>B</p>
        <p>{{this.obj.state}}</p> <!-- 值可响应 -->
    </div>
</template>
<script>
export default {
    inject: ["obj"],
}
</script>

上面例子运行后在父组件内点击按钮【修改provide的值】,子组件内使用了obj的地方也会跟着改变。

方法六、$parent/$children、ref/$refs
这几个都是通过获取组件实例的方式来通信,拿到组件后就可以直接组件的数据和函数。
$parent:当前组件的父组件实例(有可能为空,即当前组件是顶级组件的时候)
$children:当前实例的直接子组件,返回值是个数组。使用的时候需要注意 $children 并不保证顺序,也不是响应式的。
这个比较简单,就不上代码举例了,this.$parent, this.$children即可获取到实例。

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例: $refs:一个对象,持有注册过 ref 的所有 DOM 元素和组件实例。
这两个配合使用,下面举例说明

//父组件
<template>
    <div class="A">
        <button @click="onclick">访问B组件的数据</button>
        <!--1、通过 ref 注册组件引用信息, 名字自定义 -->
        <B ref="refB"></B> 
    </div>
</template>

<script>
import B from './B.vue'
export default {
    components: {B},
    methods: {
        onclick(){
            //2、通过$refs[ref名字]获取组件实例
            const bCom = this.$refs["refB"];
            bCom.num += 1;//直接调用变量修改值
            bCom.add();//调用函数
        }
    },
}
</script>

//子组件
<template>
    <div class="B">
        <p>B</p>
        <p>{{num}}</p>
    </div>
</template>
<script>
export default {
    data() {
        return {
            num: 1,
        }
    },
    methods: {
        add(){
            this.num += 1;
        }
    },
}
</script>