vue组件中的数据传递最常见的就是父子组件之间的传递。父传子通过props向下传递数据给子组件;子传父通过$emit发送事件,并携带数据给父组件。而有时两个组件之间毫无关系,或者他们之间的结构复杂,这时就要用到 vue 中的事件总线 EventBus的概念
EventBus的简介
EventBus 又称为事件总线。在 Vue 中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。
EventBus的用途
- 解决一个页面调用另一个页面的方法
- 解决父子组件、兄弟组件间的通信
EventBus的使用
一、初始化
首先创建事件总线并将其导出,以便其它模块可以使用或者监听它。实质上 EventBus 是一个不具备 Dom 的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便。有两种方法可以创建EventBus :
方法一: 新创建一个 .js 文件,比如eventBus.js
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
方法二: 直接在项目中的 main.js 初始化 EventBus
// main.js
Vue.prototype.$EventBus = new Vue()
注意,这种方式初始化的 EventBus 是一个全局的事件总线。
EventBus 创建完毕后, 需要在组件中加载它,并且调用同一个方法,就如同父子组件中互相传递消息一样。
二、发送事件
若有两个Vue页面需要通信: A 和 B ,A页面 在按钮上面绑定了点击事件,发送一则消息通知 B页面,且A页面对num值进行了监听,当num发生变化时,向B页面发生最新的num值。
<!-- A.vue 页面A -->
<template>
<span>{{ num }}</span>
<button @click="sendMsg">发送信息</button>
<button @click="doMethod">调用方法</button>
</template>
<script>
// A,B页面同时调用EventBus
import { EventBus } from '@/eventBus'
export default {
data() {
return {
num: 1,
}
},
watch: {
// 监听 num, 当 num 发生变化时,向 B 页面发送最新的 num 值
num(newValue, oldValue) {
function sendNumMsg(params) {
EventBus.$emit('aMsgEB', 'A发送的num为:', params)
}
return sendNumMsg(newValue)
},
},
methods: {
sendMsg() {
// 手动点击按钮时,向 B 页面发送 num 的值
EventBus.$emit("aMsgEB", '来自A页面的信息', num)
},
doMethod() {
const params = { id: id }
// 点击按钮时,向 B 页面发送 参数,调用B页面方法
EventBus.$emit("getDataEB", params)
}
}
}
</script>
三、接收事件
若有两个Vue页面需要通信: A 和 B ,A页面 在按钮上面绑定了点击事件,发送一则消息通知 B页面,且A页面对num值进行了监听,当num发生变化时,向B页面发生最新的num值。
<!-- B.vue 页面B -->
<template>
<span>A页面的信息: {{ msg }}</span>
<span>A页面num值: {{ numA }}</span>
</template>
<script>
// A,B页面同时调用EventBus
import { EventBus } from '@/eventBus'
export default {
data(){
return {
msg: '',
numA: ''
}
},
mounted() {
// 监听A页面发送'aMsgEB', 获取到来自A页面的信息
EventBus.$on("aMsgEB", (msg, val) => {
// A发送来的消息
this.msg = msg
this.numA = val
})
// 若 页面重新进入时,可能造成内存泄漏,在$on接收到事件之前先解绑事件($off)
// this.EventBus.$off("getDataEB")
// 监听A页面发送'getDataEB'
EventBus.$on("getDataEB", (val) => {
console.log('监听到参数变化', val)
// 开始调用方法
this.getList(val)
})
},
methods: {
getList(data){
console.log('B页面的方法调用成功')
}
}
}
</script>
同理我们也可以在 B页面 向 A页面 发送消息。主要用到以下两个方法:
// 发送消息
EventBus.$emit(channel: string, callback(payload1,…))
// 监听接收消息
EventBus.$on(channel: string, callback(payload1,…))
三、移除EventBus
// 调用EventBus
import { EventBus } from '@/eventBus'
// 销毁EventBus
beforeDestory() {
// 移除所有事件频道
EventBus.$off()
// 移除事件的监听
EventBus.$off('aMsgEB', {})
// 移除应用内所有对此某个事件的监听
EventBus.$off('aMsgEB')
},
EventBus.$off() 一般用在主页面,如: 若A,B页面是从C页面跳转进入的,或A,B页面为C页面的组件,则只需在C页面使用EventBus.$off()。
全局EventBus
它的工作原理是发布/订阅方法,通常称为 Pub/Sub
。
一、创建全局EventBus
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
在这个特定的总线中使用两个方法$on
和$emit
。一个用于创建发出的事件,它就是$emit
;另一个用于订阅$on
:
var EventBus = new Vue();
this.$bus.$emit('nameOfEvent', { ... pass some event data ...});
this.$bus.$on('nameOfEvent',($event) => {
// ...
})
然后我们可以在某个Vue页面使用this.$bus.$emit("sendMsg", '我是web秀');
,另一个Vue页面使用
this.$bus.$on('updateMessage', function(value) {
console.log(value); // 我是web秀
})
同时也可以使用
this.$bus.$off('sendMsg')
来移除事件监听。
使用eventbus遇到的问题:
问题一: $on重复触发、多次触发问题:
问题场景&原因:
组件1分发的事件,组件2监听。我们通常把组件2监听写在钩子函数created或mounted中,当组件2被销毁时,比如切换组件,再次切换到组件2时,写在钩子函数中的$on又监听了一次,这就造成了重复监听。后果就是组件1分发了一次事件,而组件2因为绑了多个$on监听,就会重复执行函数体。
解决办法:
在组件下次触发$on之前,用$off取消下绑定。通常绑定在钩子函数beforeDestroy()或者destroyed()中,如果使用了keep-alive包裹,可以写在deactivated钩子函数中。
// 方法一:
// 销毁EventBus
beforeDestory() {
EventBus.$off('aMsgEB')
}
// 如果使用了keep-alive包裹,可以写在deactivated钩子函数中
deactivated() {
// setTimeout 是为了让此方法异步,防止参数还没传递出去,方法就被关闭了
setTimeout(() => {
EventBus.$off("aMsgEB")
}, 2000)
},
或者在触发$on监听前,先将事件进行销毁:
// 方法二:
mounted() {
// 先销毁事件,防止重复触发
EventBus.$off("aMsgEB")
// 监听A页面发送'aMsgEB', 获取到来自A页面的信息
EventBus.$on("aMsgEB", (msg, val) => {
// A发送来的消息
this.msg = msg
this.numA = val
})
}
问题二: 第一次不触发监听事件
问题场景 & 原因:
A页面使用eventbus触发B页面的方法并跳转到B页面,B页面监听。第一次触发的时候,接收参数传值的B界面的on事件没有被触发。
当我们还在页面A的时候,页面B还没生成,也就是页面B中的 created / mounted 中所监听的来自于A中的事件还没有被触发。由于on在emit后面执行,所以这个时候当你A中emit事件的时候, 没有on来接受参数,B其实是没有监听到的,导致第一次触发时不能执行。
解决办法:
先加载B组件,添加定时器,将emit写成异步执行方法:
this.$router.push('/detailA')
setTimeout(() => {
EventBus.$emit('setDetailA', data)
}, 2000)
解决第一次不触发的根本在于:$emit事件执行前,要先保证$on已经存在了——即 $on 在 $emit 之前。