@sttot/emitter
TypeScript icon, indicating that this package has built-in type declarations

1.0.4 • Public • Published

Emitter 事件系统

能够创建可以发送、监听事件的对象。实现 PubSub 模式或观察者模式。

简单使用

emitter 有一些全局的方法(属于全局事件对象),可以在不做任何配置的情况下简单使用事件系统。

发射一个事件

可以携带事件负载(事件的额外信息)或者不带:

import { emit } from '@sttot/emitter';

emit('Event1'); // 不带事件负载
emit('Event2', { value: 1 }); // 携带负载
emit<{ value: number }>('Event2', { value: 2 }); // 可以对负载的类型进行指定
监听所发射的事件
import { on } from '@sttot/emitter';

// 无负载,第一参数是事件名称,第二个参数是回调函数
on('Event1', () => console.log('Event1 emitted!'));
// 有负载 + 指定类型
on<{ value: number }>('Event2', ({ value }) =>
  console.log('Event2 emitted!', value),
);
// 监听所有事件,回调函数的第一个参数是负载,第二个参数是事件名称
on('*', (payload, type) => console.log(payload, type));

once 和 on 一样,只是监听回调会在被触发过一次后自动销毁,即「只监听一次事件」:

once('Event1', () => {
  /* 只会执行一次 */
});

once 和 on 都会返回一个函数,调用该函数会注销刚才注册的事件监听回调函数:

const unregister = on('Event1', () => {
  /* */
});
unregister(); // 注销刚才的事件监听回调函数

这在 React 函数式编程中更好使用:

// 返回的注销函数正好作为 useEffect 生命周期结束的处理函数
// 如下代码可以做到在组件初始化时注册回调,在组件销毁时注销回调
React.useEffect(
  () =>
    on('Event', () => {
      /* */
    }),
  [],
);

同时,ononce 还拥有 listeners 属性,保存了各自的所有注册的回调函数:

on.listeners.get('Event1'); // 获取所有对 Event1 事件的回调函数

on 和 once 的回调函数推荐使用箭头函数 () => { ... },因为这样可以尽可能避免出现 this 丢失等问题。但是如果确实需要将某个类方法作为回调函数(例如在 Cocos 的组件中,或者 React 的类组件中使用)时,可以采用如下的方法:

方法一:

// 假设 aClassInstance 是一个类实例,aFunction 是其中的一个方法
on('Event1', payload => aClassInstance.aFunction(payload));

方法二:

// 第三个参数是回调函数中 this 指向的对象,必须指定,否则会产生问题
on('Event1', aClassInstance.aFunction, aClassInstance);

举一个在 Cocos 中使用的例子:

@ccclass('GameController')
class GameController extends Component {
  onEnable() {
    on('GameStart', this.onGameStart, this);
  }
  onGameStart({ mapId }: { mapId: string }) {
    //
  }
}
也可以使用 off 来注销回调函数

这一般适用于 Cocos 组件等注册与注销不能写在一起的情况:

import { off } from '@sttot/emitter';

@ccclass('GameController')
class GameController extends Component {
  onEnable() {
    // 注册
    on('GameStart', this.onGameStart, this);
    once('AnotherEvent', this.anotherListener, this);
  }
  onGameStart({ mapId }: { mapId: string }) {
    //
  }
  anotherListener() {}
  onDisable() {
    // 注销
    off('GameStart', this.onGameStart, this);
    // 注意 once 需要加第四个参数 true 来注销, false 或者不填则是注销 on
    off('AnotherEvent', this.anotherListener, this, true);
  }
}

其他用法:

// 什么都不填,会清空所有 on 和 once 注册的事件监听回调函数
off();
// 只填第一个参数,会注销所有通过 on 注册的对应事件的回调函数,注销 once 需要将第四个参数设为 true
off('Event1');
off('Event1', undefined, undefined, true);
// 填第一个和第二个参数,会注销所有通过 on 注册的对应事件的对应回调函数
off('Event1', aClassInstance.aFunction);
off('Event1', aClassInstance.aFunction, undefined, true);
// 填前三个参数,会注销通过 on 注册的对应事件、对应观察主题(就是 on 的第三个参数)对应的回调函数,主要是为了避免多个实例共同监听、注销时相互影响的情况
off('Event', aClassInstance.aFunction, aClassInstance);
off('Event', aClassInstance.aFunction, aClassInstance, true);
targetOff 注销某个观察实体的所有事件回调

当我们在使用类实例的方法作为回调函数时,我们需要将类实例作为 on 或者 once 的第三个参数传入。这实际上是相当于,当前监听这个事件的主体(target),就是该类实例,即该第三个参数:

on('XXX', a.func, a);

这里的 a 可以理解成是观察事件 XXX 的主体。类似的,该主体还可能会同时观察其他事件,这是一个 Cocos 组件的典型用法。那么,我们进一步希望在这个主体的生命周期结束时(被销毁,或者被禁用时),能够比较方便的注销所有其监听过的事件,那么可以用如下的方法:

import { targetOff, on } from '@sttot/emitter';

@ccclass('GameController')
class GameController extends Component {
  onEnable() {
    // 对若干事件发起了监听
    on('A', this.onA, this);
    on('B', this.onB, this);
    on('C', this.onC, this);
    on('D', this.onD, this);
  }
  onDisable() {
    // 全部注销
    targetOff(this);
  }
}
wairFor 异步事件阻塞

我们会遇到如下场景:一些顺序执行的动作,需要等待一些事件被触发后才激活后续的步骤。这样的代码如果使用 once 实现会出现嵌套地狱:

doSomething1();
once('A Ready', () => {
  doSomething2();
  once('B Ready', () => {
    doSomething3();
    once('C Ready', () => {
      ...
    });
  });
});

因此可以使用 waitFor 来解决(需要在异步函数中使用):

doSomething1();
await waitFor('A Ready');
doSomething2();
await waitFor('B Ready');
doSomething3();
await waitFor('C Ready');
...

进阶使用

如果直接使用全局事件对象的方法,往往不能很好的进行事件的负载类型推断,每次 emit、on 或者 once 都需要自己指定类型。如果需要类型推断和检验,或者需要多个相互独立的事件系统,可以使用 emitter 创建自己的事件系统对象:

import emitter from '@sttot/emitter';

// 对所有事件名称及其负载类型的定义
interface MyEvents {
  Event1: void;
  Event2: { value: number };
  Event3: number;
  Event4: IEvent4Payload;
}

// 定义一个自己的事件系统对象
export const myEmitter = emitter<MyEvents>();

其他文件可以引入这个自定义的事件系统对象,并获得对应的事件和负载类型提示:

import { myEmitter } from './xxx.ts';

// 如果安装了相应的插件,会对事件名称进行补全,
// 会对负载类型自动推断、验证
myEmitter.on('Event2', ({ value }) => { /* */ });

Readme

Keywords

none

Package Sidebar

Install

npm i @sttot/emitter

Weekly Downloads

0

Version

1.0.4

License

none

Unpacked Size

36.7 kB

Total Files

19

Last publish

Collaborators

  • sttot