ts-emits:TypeScript 友好的事件库,赋能类型推断与安全

295 阅读5分钟

ts-emits 是一款专为 TypeScript 开发者设计的事件库,旨在提升事件管理和类型安全。它能够充分利用 TypeScript 的类型系统,为事件的参数类型提供清晰、准确的类型推断,并且支持多种复杂场景下的参数定义,确保在开发过程中无缝体验 IDE 提供的类型提示与错误检查。

主要特点

  • 类型推断:根据事件定义自动推断参数类型,开发时无需显式指定类型。
  • 类型安全:通过 TypeScript 类型系统确保事件触发与监听过程中类型的一致性,避免类型错误。
  • 简单易用:API 简单、直观,轻松上手。

安装

你可以通过 pnpm 安装 ts-emits

pnpm add ts-emits

使用方法

基本用法

ts-emits 的基本使用与常见的事件系统相似,可以通过 mitt 函数创建事件发射器(emitter)并进行事件的监听和触发。

import { mitt } from 'ts-emits';

const emitter = mitt()

// 事件监听
emitter.on('foo', e => console.log('foo', e))

// 事件触发
emitter.emit('foo', { a: 'b' })

// 清空所有事件
emitter.all.clear()

// 使用 on 和 off 进行监听与取消监听
function onFoo() {}
emitter.on('foo', onFoo)
emitter.off('foo', onFoo)

TypeScript 类型推断

ts-emits 强调类型安全,它提供了一种优雅的方式来定义和管理事件的类型。

示例:定义事件类型

你可以通过定义一个事件类型对象来描述每个事件的参数类型。通过泛型参数传递事件类型,mitt 会自动推断事件的参数类型,并在 IDE 中显示相关的类型提示。

import { mitt } from 'ts-emits';

// 定义事件类型
type Events = {
  event1: void; // 无参数事件
  event2: string; // 单个字符串参数事件
  event3: [string, number]; // 两个参数,类型分别是 string 和 number
  event4: number[]; // 数字数组类型事件
  event5: { key: string; value?: number }; // 对象类型事件,包含可选属性
};

const emitter = mitt<Events>();

/** event1: 无参数事件 */
emitter.on('event1', () => {
  console.log('event1 triggered');
});

// 正确触发 event1
emitter.emit('event1'); // ✅ 正确用法

// 错误示例
// emitter.emit('event1', 'unexpected'); // ❌ 错误:'event1' 不接受任何参数

/** event2: 单个字符串参数事件 */
emitter.on('event2', (e) => {
  console.log('event2:', e); // e 被推断为 string
});

// 正确触发 event2
emitter.emit('event2', 'Hello World'); // ✅ 正确用法

// 错误示例
// emitter.emit('event2', 123); // ❌ 错误:类型 'number' 不能分配给类型 'string'

/** event3: 两个参数(string 和 number) */
emitter.on('event3', (str, num) => {
  console.log('event3:', str, num); // str: string, num: number
});

// 正确触发 event3
emitter.emit('event3', 'Hello', 42); // ✅ 正确用法

// 错误示例
// emitter.emit('event3', 'Hello'); // ❌ 错误:缺少参数 'num'
// emitter.emit('event3', 42, 'Hello'); // ❌ 错误:参数类型顺序不匹配

/** event4: 数字数组事件 */
emitter.on('event4', (nums) => {
  console.log('event4:', nums.join(', ')); // nums 被推断为 number[]
});

// 正确触发 event4
emitter.emit('event4', [1, 2, 3]); // ✅ 正确用法

// 错误示例
// emitter.emit('event4', 'not an array'); // ❌ 错误:类型 'string' 不能分配给类型 'number[]'

/** event5: 对象类型事件 */
emitter.on('event5', ({ key, value }) => {
  console.log('event5:', key, value); // key: string, value: number | undefined
});

// 正确触发 event5
emitter.emit('event5', { key: 'example', value: 123 }); // ✅ 正确用法
emitter.emit('event5', { key: 'example' }); // ✅ 正确用法

// 错误示例
// emitter.emit('event5', { value: 123 }); // ❌ 错误:缺少 'key' 属性
// emitter.emit('event5', 'invalid'); // ❌ 错误:类型 'string' 不能分配给类型 '{ key: string; value?: number }'

在上述示例中,事件 event1event5 都具备不同类型的参数定义,TypeScript 会在编译时根据这些类型自动推断参数类型。如果触发事件时传递了错误类型的参数,IDE 会立即报错,帮助开发者避免类型错误。

继承自 EventEmitter

如果需要自定义事件发射器并继承 ts-emits 提供的功能,可以创建一个新的类并扩展 EventEmitter 类。这样,你不仅可以享受类型推断,还能自定义事件发射器的行为。

import { EventEmitter } from 'ts-emits';

class MyEmitter extends EventEmitter<Events> {
  logEvent(message: string) {
    console.log('Log:', message);
  }
}

const myEmitter = new MyEmitter();
myEmitter.on('login', ({ username }) => {
  myEmitter.logEvent(`${username} logged in`);
});

myEmitter.emit('login', { username: 'Bob' });

mitt 与 ts-emits 的对比

mitt 是一款非常流行且轻量的事件库,但它在 TypeScript 中的使用体验却存在一定的局限性,尤其在涉及多个参数类型时,它的类型推断能力相对较弱。让我们来看几个对比例子。

1. 单一参数事件

ts-emits:

type Events = {
  event1: string;
};

const emitter = mitt<Events>();
emitter.on('event1', (e) => {
  console.log(e); // 'e' 被推断为 'string'
});

emitter.emit('event1', 'Hello World'); // ✅ 正确用法

ts-emits 中,event1 的类型已清晰地定义为 string,因此开发者可以直观地知道事件参数的类型。

mitt:

type Events = {
  event1: string;
};
const emitter = mitt<Events>();
emitter.on('event1', (e: string) => {
  console.log(e); // 'e' 被显式指定为 'string'
});

emitter.emit('event1', 'Hello World'); // ✅ 正确用法

mitt 中,虽然可以手动指定事件参数的类型,但这依赖于显式的类型注解,并且如果事件类型复杂,类型推断会变得繁琐。

2. 多个参数事件

ts-emits:

type Events = {
  event2: [string, number];
};

const emitter = mitt<Events>();
emitter.on('event2', (str, num) => {
  console.log(str, num); // 'str' 被推断为 'string', 'num' 被推断为 'number'
});

emitter.emit('event2', 'Hello', 42); // ✅ 正确用法

ts-emits 中,可以轻松地定义一个多个参数的事件,且 TypeScript 会根据定义的参数类型自动推断出每个参数的类型。

mitt:

const emitter = mitt();
emitter.on('event2', (str, num) => {
  console.log(str, num); // 'str' 和 'num' 的类型无法自动推断
});

emitter.emit('event2', 'Hello', 42); // ✅ 正确用法,但没有类型推断

mitt 中,多个参数事件的类型推断是非常有限的。如果不显式指定参数类型,编译器不会提示类型错误,可能导致类型不一致的错误,开发者需要自己处理类型注解。

项目地址

你可以在 GitHub 上找到 ts-emits 的源代码和更多文档,了解如何在你的项目中使用它。

如果你需要在 TypeScript 中构建类型安全的事件系统,尤其是当事件涉及多个参数时,ts-emits 提供了更为出色的类型推断与类型安全保障。而 mitt 虽然轻量且易用,但在复杂事件参数类型的处理上则显得稍显不足。