代码就跟生活一样,今天我们聊聊事件总线
聊之前我们需要知道他是干嘛,为啥要有他.
事件总线:
我是一个通信手段,从前组件之间通信都需要靠面对面通信,一个传一个.
有了我不一样了.我跟电话微信一样,有事情直接电话联系呗.不用通过七大姑八大姨转达
应用场景:跨组件通信时,需要处理响应某件事情.
实际开发中,原来的业务场景是可以满足当时的业务需求的,但是后续新的场景添加进去,势必会出现跨组件通信的. 为了后续的可维护性,代码冗余性:
我们使用事件总线, 脱离组件.只针对某件事情 去做具体操作.让事件彼此通信,不依靠组件
如何实现事件总线这个功能呢?我们来分析一下他的逻辑
注释: 想直接看最终结果的 可以直接看--最终源码块.接下来的所有内容都是一些逻辑引导.方便小白理解,代码量很小
首先我们需要知道自己想要实现的功能
-
提供监听某个事件的接口,并告知监听到的后续操作
-
提供取消监听的接口
-
触发事件的接口(可传递数据)
-
触发事件后自动通知监听者
监听事件 on(监听事件名,监听到需要的操作)
取消监听 off(监听事件名,需要取消的操作)
通知监听者---触发监听事件 emit(要触发的事件,想要传递的消息---)
根据已知功能,设计一个数据结构
根据已有信息 我们现在需要设置一个简单的数据结构,然后通过操作数据结构,来得到我们想实现的目的
需要存放事件名,对应事件操作函数,自然而然我们想到了对象
设计结构初稿
const events = {
'点击事件----类似电话号码': '点击事件对应的操作---我说话你不要插嘴但是要及时恢复',
'滚动事件': '滚动事件的操作',
'诸如此类': '诸如此类的操作'
}
然后我们存放的事件肯定不止一个,比如点击事件,不同dom,比如button的点击事件肯定是不一样的,所以我们需要存放多个事件
const evenBus = {
'点击事件': [操作一,操作2],
'滚动事件': [操作一,操作2],
'诸如此类': [fn1,fn2,fn3]
有了数据结构,就跟你有了女神的联系方式一样,那我们就可以搞事情了
简单的思路
-
就是监听到点击事件就往点击事件里面加一个操作
-
点击时,遍历触发对应数组内所有的操作函数
-
不想要这个操作时,在数组中删除掉该操作
那现在开始实现我们之前想要的功能了, 一个一个填充进去
监听女神的电话号码
// 数据结构
const Events = {}
// 功能:提供监听某个事件的接口,并告知监听到的后续操作
// 数据结构: 监听到对应事件,往数组中添加对应操作
//--函数结构 监听事件 on(监听事件名,监听到需要的操作)
function $on(eventName, handler) {
//如果数据结构 不存在 eventName 这个事件 将其默认为一个数组或者Set 后续再把操作add进去
//为了避免重复这里用Set 可以理解为一个没有排序且自动去重的数组
if (!Events[eventName]) {
Events[eventName] = new Set()
}
Events[eventName].add(handler)
}
$on('女神的电话号码', '监听到女神来电后必须得震动加提醒呀')
$on('女神的电话号码', '打通后我要跟女神告白')
console.log('来看下执行$on后 数据结构是不是多了一个女神的电话', Events)一个女神的电话', Events)
// * 提供取消监听的接口
// * 触发事件的接口(可传递数据)
// * 触发事件后自动通知监听者
接下来我们试试 取消监听后的对应操作
记得再上面代码的基础上往下写额 ,不要单独运行下面的代码. 因为我们定义的数据结构还在上面
//取消监听 off(监听事件名,需要取消的操作)
function $off(eventName,handler){
if (!Events[eventName]) {
//如果压根没监听这个事件----比如你好兄弟的号码
console.log(eventName,'这谁呀,压根就没监听,不用理不用取消,压根没得取消 直接返回吧')
return
}
Events[eventName].delete(handler)
}
思虑再三,告白还需谨慎,那么我们把告白操作删掉把
$off('女神的电话号码', '打通后我要跟女神告白')
console.log('看下告白操作是不是没了', Events)
还差一个触发事件对吧,来吧来吧
// * 触发事件的接口(可传递数据)
// emit(要触发的事件,想要传递的消息---)
function $emit(eventName, ...args) {
const handlers = Events[eventName]
if (!handlers) return
// 遍历执行数组内的所有内容 ..因为操作肯定是函数,所以前面的中文只是方便大家理解 后续改成对应操作函数额
for (const handler of handlers) {
handler(...args)
}
}
我们试试这个$emit操作
function remind() {
console.log('提醒你的女神来电了!')
}
//监听这个事件放入操作
$on('女神电话-111111', remind)
//触发这个事件,执行该事件的所有操作
$emit('女神电话-111111')
// * 触发事件后自动通知监听者
完整实现代码 过滤引导
// 数据结构
const Events = {}
export default {
$on(eventName, handler) {
if (!Events[eventName]) {
Events[eventName] = new Set()
}
Events[eventName].add(handler)
},
// * 提供取消监听的接口
$off(eventName, handler) {
if (!Events[eventName]) {
return
}
Events[eventName].delete(handler)
},
// * 触发事件的接口(可传递数据)
$emit(eventName, ...args) {
const handlers = Events[eventName]
if (!handlers) return
for (const handler of handlers) {
handler(...args)
}
}
}
以上内容正常情况小白应该也是完全没问题的,如果有问题赶紧打下基础或者针对不理解的地方搜索了解
当然在vue2中我们还有更快的实现方法
import Vue from 'vue';
export default new Vue({})
两行代码就搞定了 气不气.是不是觉得白学了
稳住,自己手写一遍底层实现能加升理解嘛,而且你应用的场景就没有限制了.
tip:
监听不用了:记得取消掉额,一直监听很呆的
讲到打电话,突然觉得蛮好玩的,给你做一个测试demo嘛,不考虑美观啊,就逻辑
demo 速写
为了节省时间,直接在Vue3官网中自带的单文件组件的环境中编写了,可以直接测试单文件组件
大概功能
- 点击准备好啦----意味着开始监听--时刻看着女神的动态
- 这个时候女神发动态就触发了 我们就能看到女神动态信息,及时回复了
- 点击不准备时,意味着. 我压根没关注女神,他做什么我都不知道
<script setup>
import { ref, computed } from "vue";
import eventBus from "./eventBus.js";
const msg = ref("Hello World!");
const boyurl = ref(
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F6b7fca1df8bd2c134d0ab7050a740f08dccafb693044b7-thDTX8_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1666588736&t=60cf73007d72cc592eb308ccfe73721a"
);
const ready = ref(false);
const state = computed(() => {});
const call =(...msg)=>{
alert('你的女神发动态啦:\n'+msg)
}
const hander = function () {
ready.value = !ready.value;
if (!ready.value) {
boyurl.value =
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F6b7fca1df8bd2c134d0ab7050a740f08dccafb693044b7-thDTX8_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1666588736&t=60cf73007d72cc592eb308ccfe73721a";
eventBus.$off('fishing',call)
} else {
boyurl.value =
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.soogif.com%2FW1RsUzGVmtjEoieJj4Oh29Rf4y3alG5C.gif_s400x0&refer=http%3A%2F%2Fimg.soogif.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1666589358&t=adebeabf43c7c617d89ddf057afff7d5";
eventBus.$on('fishing',call)
}
};
const share = function () {
eventBus.$emit("fishing", "交个朋友?");
};
</script>
<template>
<div class="container">
<div class="boy">
<img :src="boyurl" />
<button class="callback">
{{ ready ? "坐等女神动态......." : "出去玩喽,你找我....我也不知道" }}
</button>
<button class="state" @click="hander">
{{ ready ? "不准备了" : "准备好啦" }}
</button>
</div>
<div class="gril">
<img src="" />
<button @click="share">作为女神,我今天发一条朋友圈吧</button>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.boy,
.gril {
display: flex;
align-items: center;
justify-content: center;
}
button {
margin: 20px;
width: 300px;
height: 50px;
}
.state {
width: 100px;
}
img {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
}
进阶- 类的方式实现
vue3 中我们可以用混合类 来玩一下这个,妈妈再也不用担心我忘了取消监听了. 上面用的Set 下面用数组吧.都差不多 下面的代码我不说废话了,大家试着自己用类写一个
import { getCurrentInstance } from 'vue'
class EventBus {
constructor(app) {
if (!this.handles) {
Object.defineProperty(this, 'handles', {
value: {},
enumerable: false
})
}
this.app = app
// _uid和EventName的映射
this.eventMapUid = {}
}
setEventMapUid(uid, eventName) {
if (!this.eventMapUid[uid]) {
this.eventMapUid[uid] = []
}
this.eventMapUid[uid].push(eventName)
// 把每个_uid订阅的事件名字push到各自uid所属的数组里
}
$on(eventName, callback, vm) {
// vm是在组件内部使用时组件当前的this用于取_uid
if (!this.handles[eventName]) {
this.handles[eventName] = []
}
this.handles[eventName].push(callback)
this.setEventMapUid(vm._uid, eventName)
}
$emit() {
let args = [...arguments]
let eventName = args[0]
let params = args.slice(1)
if (this.handles[eventName]) {
let len = this.handles[eventName].length
for (let i = 0; i < len; i++) {
this.handles[eventName][i](...params)
}
}
}
$offVmEvent(uid) {
let currentEvents = this.eventMapUid[uid] || []
currentEvents.forEach(event => {
this.$off(event)
})
}
$off(eventName) {
delete this.handles[eventName]
}
}
let $EventBus = {}
$EventBus.install = (app) => {
app.config.globalProperties.$eventBus = new EventBus(app)
app.mixin({
beforeUnmount() {
const currentInstance = getCurrentInstance();
// 拦截beforeUnmount钩子,自动销毁自身所有订阅的事件
this.$eventBus.$offVmEvent(currentInstance._uid)
}
})
}
export default $EventBus