npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sttot/emitter

v1.0.4

Published

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

Downloads

1

Readme

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 }) {
    //
  }
}

这一般适用于 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);

当我们在使用类实例的方法作为回调函数时,我们需要将类实例作为 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);
  }
}

我们会遇到如下场景:一些顺序执行的动作,需要等待一些事件被触发后才激活后续的步骤。这样的代码如果使用 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 }) => { /* */ });