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 }'
在上述示例中,事件 event1 到 event5 都具备不同类型的参数定义,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 虽然轻量且易用,但在复杂事件参数类型的处理上则显得稍显不足。