iOS

如何建立自己的 event bus

在不断填坑中前进。。

Posted by 三十一 on April 17, 2023

如何建立自己的 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 就完成了,使用了下,还不错,功能已经实现了。

但是 如果同时有两个组件实例订阅了相同的事件呢? componentAcomponentB 中都订阅了名称为 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();
    }
  }
}

这样就满足了多个实例订阅同一个事件的问题,这样就好了吗?如果我们想不再订阅这个事件要怎么处理?

SYEventBusCallBackContainer 中提供解除绑定的方法。

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