JavaScript 发布-订阅设计模式实现 React EventBus(相当于vue的$Bus)非父子之间通信

2023-05-25,,

提前声明: 我没有对传入的参数进行及时判断而规避错误,仅仅对核心方法进行了实现;

解决了react的非父子间的通信;

参考文档:https://github1s.com/browserify/events/blob/main/events.js

https://www.npmjs.com/package/events

https://github.com/browserify/events

1.其中的一种实现的方式

首先先新建一个文件eventBus.tsx

class EventBus {
constructor() {
this.events = this.events || new Map(); // 储存事件/回调键值对
this.maxListeners = this.maxListeners || 10; // 设立监听上限
} // 触发名为type的事件
emit(type, ...args) {
let handler = null;
handler = this.events.get(type);
console.log(' ~ file: eventBus.js:11 ~ EventBus ~ emit ~ handler:', handler, args);
if (handler === undefined) {
return false;
}
if (Array.isArray(handler)) {
// 如果是一个数组说明有多个监听者,需要依次此触发里面的函数
// eslint-disable-next-line no-plusplus
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args);
} else {
handler[i].call(this);
}
}
} else {
// 单个函数的情况我们直接触发即可
// eslint-disable-next-line no-lonely-if
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
}
return true;
} // 监听名为type的事件
on(type, fn) {
const handler = this.events.get(type);
if (!handler) {
this.events.set(type, fn);
} else if (handler && typeof handler === 'function') {
// 如果handler是函数说明只有一个监听者
this.events.set(type, [handler, fn]); // 多个监听者我们需要用数组储存
} else {
handler.push(fn); // 已经有多个监听者,那么直接往数组里push函数即可
}
} // eslint-disable-next-line consistent-return
remove(type, fn) {
const handler = this.events.get(type); // 获取对应事件名称的函数清单
if (handler && typeof handler === 'function') {
// 如果是函数,说明只被监听了一次
this.events.delete(type, fn);
} else {
if (handler === undefined) {
return false;
}
let position = null;
// 如果handler是数组,说明被监听多次要找到对应的函数
// eslint-disable-next-line no-plusplus
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
position = i;
} else {
position = -1;
}
}
// 如果找到匹配的函数,从数组中清除
if (position !== -1) {
// 找到数组对应的位置,直接清除此回调
handler.splice(position, 1);
// 如果清除后只有一个函数,那么取消数组,以函数形式保存
if (handler.length === 1) {
this.events.set(type, handler[0]);
}
} else {
return this;
}
}
}
} const eventBus = new EventBus();
export default eventBus; // 简单实现的发布订阅模式 也是对的
// class EventEmitter {
// constructor() {
// this.eventBus = this.eventBus || {};
// } // emit(type, params) {
// this.eventBus.type.forEach(item => {
// item(params);
// });
// } // on(type, fn) {
// if (this.eventBus.type) {
// this.eventBus.type.push(fn);
// } else {
// this.eventBus.type = [fn];
// }
// } // remove(type, fn) {
// if (this.eventBus[type]) {
// delete this.eventBus.type
// }
// }
// } // const eventBus = new EventEmitter();
// export default eventBus;

然后再组件A使用=>接收

import React, { useState, useEffect, useCallback } from 'react';
// import eventBus from '@/utils/events';
import eventBus from '@/utils/eventBus'; const TopBox = () => {
const [num, setNum] = useState(0); const addNum = useCallback((message: any) => {
setNum(message);
}, []); useEffect(() => {
eventBus.on('emit', addNum);
return () => {
if(eventBus) {
eventBus.remove('emit', addNum);
}
};
}, []); return (
<div>
<span>我也不知道是什么111: {num}</span>
</div>
);
}; export default TopBox;

然后再组件B使用=>触发

import React, { useState } from 'react';
import { Button } from 'antd';
// import eventBus from '@/utils/events';
import eventBus from '@/utils/eventBus'; const TopBox = () => {
const [num,setNum] = useState(0) const addNum = () => {
const numNew = num + 1;
setNum(numNew);
eventBus.emit('emit', numNew)
} return (
<div>
<Button onClick={() => {addNum()}}>按钮</Button>
<span>我也不知道是什么:{num}</span>
</div>
);
}; export default TopBox;

2.其中的另一种实现的方式

安装这个events插件

yarn add events

新建一个文件evnets.ts

import { EventEmitter } from 'events';
export default new EventEmitter();

组件A使用

import React, { useState, useEffect, useCallback } from 'react';
import eventBus from '@/utils/events';
// import eventBus from '@/utils/eventBus'; const TopBox = () => {
const [num, setNum] = useState(0); const addNum = useCallback((message: any) => {
setNum(message);
}, []); useEffect(() => {
eventBus.on('emit', addNum);
return () => {
if(eventBus) {
// eventBus.remove('emit', addNum);
eventBus.removeListener('emit', addNum);
}
};
}, []); return (
<div>
<span>我也不知道是什么111: {num}</span>
</div>
);
}; export default TopBox;

组件B使用

import React, { useState } from 'react';
import { Button } from 'antd';
import eventBus from '@/utils/events';
// import eventBus from '@/utils/eventBus'; const TopBox = () => {
const [num,setNum] = useState(0) const addNum = () => {
const numNew = num + 1;
setNum(numNew);
eventBus.emit('emit', numNew)
} return (
<div>
<Button onClick={() => {addNum()}}>按钮</Button>
<span>我也不知道是什么:{num}</span>
</div>
);
}; export default TopBox;

JavaScript 发布-订阅设计模式实现 React EventBus(相当于vue的$Bus)非父子之间通信的相关教程结束。

《JavaScript 发布-订阅设计模式实现 React EventBus(相当于vue的$Bus)非父子之间通信.doc》

下载本文的Word格式文档,以方便收藏与打印。