vue 组件通信方式 ,父子、隔代、兄弟 三类通信,六种方法
Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
(1)props / $emit
适用 父子组件通信
父组件注入,子组件接收。
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
注意:props是单向数据流,既只能从父级传到子级,如果想要达到双向,子级能够修改父级,则需要给props加sync修饰符。(文章后部分有详细介绍)
(2) ref
与 $parent / $children
适用 父子组件通信
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children
:访问父 / 子实例
(3)$attrs / $listeners
适用于 隔代组件通信
$attrs:
包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。
当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
(4)provide / inject
适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
// 例子 全局事件中心
// App.vue
export default {
name: "App",
provide() {
return {
reload: this.loadFun,
};
},
methods: {
loadFun() {
this.isRouterAlive = false;
this.$nextTick(() => {
this.isRouterAlive = true;
});
},
}
}
// b.vue
export default {
name:"xxx",
inject: ["reload"],
// 然后可以调用this.reload()方法
}
(5)EventBus ($emit / $on)
适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
// 例子
// main.js
Vue.prototype.$EventBus = new Vue()
// a.vue 传递数据
this.$EventBus.$emit("sendMsg","发送传递数据 "+ Math.random()*10000)
// b.vue 接收数据
this.$EventBus.$on("sendMsg",(params)=>{
this.msg=params
})
(6)Vuex
适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
vuex的详细使用方法: vuex管理状态仓库使用详解
Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你知道的回答出越多方法越加分,表明你对 Vue 掌握的越熟练。
举例props使用
a.vue 引用了一个detail组件
// a.vue
<Detail :goodsId="goodsId" :otherData.sync="otherData" @doSomeThing="doThing" ></Detail>
// Detail.vue
props:{
goodsId:{
type: String,
},
otherData:{
type: Object,
}
}
// goodsId不能修改
// otherData可以这样修改
this.$emit('update:otherData', newData) // 把父组件otherData修改成newData
// 父组件doThing方法这样调用
this.$emit('doSomeThing', params) // 调用父组件doThing方法,并传入params参数
详解eventBus通信方法
第一步:首先需要创建事件总线并将其导出,以便其它模块可以使用或者监听它。
两个初始化事件中心的方法:
// 方法一:创建一个event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 方法二:main.js里直接挂载到vue原型上
// 注意,这种方式初始化的EventBus是一个全局的事件总线。
Vue.prototype.$EventBus = new Vue()
第二步:创建了 EventBus ,接下来你需要做到的就是在你的组件中加载它,并且调用同一个方法,就如你在父子组件中互相传递消息一样。
假设你有两个Vue页面需要通信: A 和 B ,A页面 在按钮上面绑定了点击事件,发送一则消息,想通知 B页面。
<!-- A.vue -->
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
import { EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
// 方法1
EventBus.$emit("aMsg", '来自A页面的消息');
// 方法2
this.$EventBus.$emit("aMsg", '来自A页面的消息');
}
}
};
</script>
接下来,我们需要在 B页面 中接收这则消息。
<!-- B.vue -->
<template>
<p>{{msg}}</p>
</template>
<script>
import {
EventBus
} from "../event-bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
// 方法1
EventBus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
});
// 方法2
this.$EventBus.$on("aMsg", (msg) => {
this.msg = msg;
});
}
};
</script>
同理我们也可以在 B页面 向 A页面 发送消息。这里主要用到的两个方法:
// 发送消息
EventBus.$emit(channel: string, callback(payload1,…))
EventBus.$emit(eventName, params)
// 监听接收消息
EventBus.$on(channel: string, callback(payload1,…))
EventBus.$on(eventName, callback(params))
如果使用不善,EventBus会是一种灾难,到底是什么样的“灾难”了?大家都知道vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。还要就是如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理EventBus在项目中的关系。通常会用到,在vue页面销毁时,同时移除EventBus事件监听。
如果想移除事件的监听,可以像下面这样操作:
import {
eventBus
} from './event-bus.js'
// ...
// 在b页面里取消监听接收,否则会数据重复 a最好也写下
beforeDestroyed(){
EventBus.$off('aMsg', {})
// this.$EventBus.$off('aMsg', {})
// EventBus.$off()
}
$on
事件是不会自动清楚销毁的,需要我们手动来销毁,否则在b组件每次加载一次就会创建一个监听,会重复监听到数据。
可以使用 EventBus.$off('aMsg')
来移除应用内所有对此某个事件的监听。
或者直接调用 EventBus.$off()
来移除所有事件频道,不需要添加任何参数 。
情况1:bus进行页面传参
总结: 所以,如果想要用bus 来进行
页面
组件之间的数据传递,需要注意两点:
1、组件A$emit
事件应在beforeDestory生命周期内。
2、其次,组件B内的$on记得要销毁。
因为页面跳转的时候 ,a页面在之前已经emit了,但是b页面首次并没有created,b页面还监听不到。
可以把A页面组件中的emit事件写在beforeDestory中去。因为这个时候,B页面组件已经被created了,也就是我们写的$on事件已经可以触发了
所以可以,在beforeDestory的时候,$emit事件。例如:
editList (index, date, item) {
// 点击进入编辑的页面,需要传递的参数比较多。
this.item = item.type
this.date = date
this.$router.replace({path: '/moneyRecord'})
}
// 重新在data属性内部定义新的变量,来存储要传过去的数据;
然后:
beforeDestroy () {
bus.$emit('pushDataToBpage', {
item: this.item,
date: this.date
})
},
情况2:bus进行组件传参
如果是同一个页面,两个组件传参,A里啥时候用啥时候 emit 就行 不用放到beforeDestroy里
然后B组件里 在mounted 里 先$off
再$on
或者在B组件的 mounted里$on
在beforeDestroy 里$off
总之记得要手动销毁 bus就行
【参考eventbus实战记录:https://www.jianshu.com/p/fde85549e3b0】