如何建立自己的 event bus
自定义 eventbus
,首先要考虑外部如何调用,暂定如下:
import eventBus from './components/SYEventBus';
eventBus.addListener('eventName', (p) => {
console.log('p: ', p);
});
eventBus.emit('eventName', { name: 'Yunis' });
确定了 API
如何调用后,来具体的实现 API
内部的具体逻辑。
代码如下
// 事件总线
class SYEventBus {
// 订阅者的容器
targetContainer: {
[eventName: string]: (p: any) => void;
} = {};
addListener(eventName: string, callback: (parameter: any) => void) {
if (!eventName || !callback) return;
this.targetContainer[eventName] = callback;
}
emit(eventName: string, parameter?: any) {
let callback = this.targetContainer[eventName];
if (callback) {
callback(parameter);
}
}
}
const eventBus = new SYEventBus();
export default eventBus;
这样一个简单的 eventbus
就完成了,使用了下,还不错,功能已经实现了。
但是 如果同时有两个组件实例订阅了相同的事件呢? componentA
和 componentB
中都订阅了名称为 event_pass
的事件。那么这就会产生一个问题,后一个订阅该事件的实例会把前一个订阅该事件的实例给替换掉。使前面订阅事件的实力无法响应回调。
知道问题所在后,我们来修改代码。
首先定义一个 CallBackContainer
类,根据事件名称来保存回调。回调可以有多个。
那么 CallBackContainer
中就需要有一个数组来保存回调方法,同时需要提供订阅事件、通知事件的方法。
实现如下,具体的代码已经添加注释。:
// 定义一个事件名称相同的观察者容器
class CallBackContainer {
// 事件名称
eventName: string;
// 事件响应者回调集合
callbacks: { [bid: symbol]: (parameter: any) => void } = {};
// container: ((parameter: any) => void)[] = [];
constructor(eventName: string) {
this.eventName = eventName;
}
/**
* @func subscribe
* @desc 注册观察者回调,当根据事件名称调用时,会到这个集合中查找回调方法
* @param {callback: 事件回调}
* @return { 返回一个带有解除绑定方法的实例 }
*/
subscribe(callback: (parameter: any) => void) {
// 获取与回调方法绑定的key(唯一)
let key = this.nextKey();
this.callbacks[key] = callback;
}
/**
* @func nextKey
* @desc 获取绑定事件的唯一key
* @param {}
* @return {唯一key}
*/
nextKey() {
let symbol = Symbol(this.eventName);
return symbol;
}
//获取当前事件的全部绑定key(然后根据key,获取到回调方法)
allKeys() {
const allSymbols = Object.getOwnPropertySymbols(this.callbacks);
return allSymbols;
}
// 通知观察者
/**
* @func publish
* @desc 知观察者
* @param {parameter: 事件传递的参数}
* @return {}
*/
publish(parameter?: any) {
// 获取全部的绑定key
let allSymbols = this.allKeys();
allSymbols.forEach((key) => {
// 根据key 获取回调方法
const call = this.callbacks[key];
call(parameter);
});
}
}
在原来的 SYEventBus
中,将使用 CallBackContainer
的实例来保存回调方法以及事件通知,修改代码如下。
class SYEventBus {
// 订阅者的容器
targetContainer: {
[eventName: string]: CallBackContainer;
} = {};
addListener(eventName: string, callback: (parameter: any) => void) {
if (!eventName || !callback) return;
let callbacks = this.targetContainer[eventName];
if (!callbacks) {
callbacks = new CallBackContainer(eventName);
this.targetContainer[eventName] = callbacks;
}
callbacks.subscribe(callback);
}
emit(eventName: string, parameter?: any) {
let callback = this.targetContainer[eventName];
if (callback) {
callback.publish();
}
}
}
这样就满足了多个实例订阅同一个事件的问题,这样就好了吗?如果我们想不再订阅这个事件要怎么处理?
为 SYEventBus
和 CallBackContainer
中提供解除绑定的方法。
在 CallBackContainer
中修改如下:
// 定义一个事件名称相同的观察者容器
class CallBackContainer {
...
emptyHooks: (key: string) => void;
...
// container: ((parameter: any) => void)[] = [];
constructor(eventName: string, emptyHooks: (key: string) => void) {
...
this.emptyHooks = emptyHooks;
}
/**
* @func subscribe
* @desc 注册观察者回调,当根据事件名称调用时,会到这个集合中查找回调方法
* @param {callback: 事件回调}
* @return { 返回一个带有解除绑定方法的实例 }
*/
subscribe(callback: (parameter: any) => void) {
...
let key = this.nextKey();
return {
// 解除绑定
unsubscribe: () => {
delete this.callbacks[key];
const allKeys = this.allKeys();
if (allKeys.length === 0) {
this.emptyHooks(this.eventName);
}
},
};
}
...
}
在 SYEventBus
修改如下:
// 事件总线
class SYEventBus {
...
addListener(eventName: string, callback: (parameter: any) => void) {
...
return callbacks.subscribe(callback);
}
...
}
再需要解除绑定的地方,这样调用:
let unsubscribeBus = eventBus.addListener('eventName', (p) => {
console.log('p: ', p);
});
unsubscribeBus?.unsubscribe();
好了,这样一个自定义的 eventbus
算是完工了。
具体代码 git eventbus