我正在努力改进 Typescript 类型,并且需要一个类型安全的事件发射器。我现在已经尝试了很多不同的方法,但我似乎无法完全正确地解析类型。你能看出我哪里出错了吗?
在下面的示例中,我有一个“事件”类型,它将事件名称映射到必须与该事件一起传递的参数。因此,如果我发出“Foo”,我还必须传递一个“bar”字符串,并且侦听器应该知道有一个“bar”属性要读取。
interface Events {
Foo: {
bar: string;
}
}
type EventKeys = keyof Events
class Emitter {
...
emit<K extends EventKeys> (title: K, value: Events[K]): void {
// With this signature I want to require if the caller specifies a title of "Foo"
// then they must specify value as "{bar: string}". This part looks to work great!
this.emitter.emit("connection", [title, value])
}
public on (listener: any): void {
// I use "any" here because this part of the code isn't super relevant to this example
this.emitter.on('connection', listener.event.bind(f))
}
}
class Listener {
...
event<K extends EventKeys> ([title, value]: [title: K, value: Events[K]]): void {
switch(title) {
case "Foo":
console.log(value)
// Here "value" is of type "Events[K]",
// which I take to mean it should know it's type "Events[Foo]"
// or actually "{bar: string}",
// but I don't get the autocompletion I expect.
break
}
}
}
难道不能从 {bar: string}
这样的泛型中得到 Events[K]
吗?
答案 0 :(得分:0)
这里有一些你想要的东西。 emit()
现在是类型安全的,您可以按 Tab 键自动完成可能的事件名称。 需要 TypeScript 4+。
演示:https://repl.it/@chvolkmann/Typed-EventEmitter
import { EventEmitter } from 'events'
interface EventTree {
Connected: {
foo: string;
};
}
type EventName = keyof EventTree;
type FormattedEvent<E extends EventName> = [E, EventTree[E]];
class MyListener {
handleEvent<E extends EventName>([event, args]: FormattedEvent<E>) {
console.log(`Event "${event}" happened with args`, args);
}
}
class MyEmitter {
protected emitter: EventEmitter
constructor() {
this.emitter = new EventEmitter()
}
emit<E extends EventName>(name: E, args: EventTree[E]) {
this.emitter.emit('connection', [name, args]);
}
registerListener(listener: MyListener) {
// The typing here is a bit overkill, but for demostration purposes:
const handler = <E extends EventName>(fEvent: FormattedEvent<E>) => listener.handleEvent(fEvent)
this.emitter.on('connection', handler)
}
}
const emitter = new MyEmitter()
emitter.registerListener(new MyListener())
// This supports autocompletion
// Try emitter.emit(<TAB>
emitter.emit('Connected', { foo: 'bar' })
// TypeScript won't compile these examples which have invalid types
// > TS2345: Argument of type '"something else"' is not assignable to parameter of type '"Connected"'.
// emitter.emit('something else', { foo: 'bar' })
// > TS2345: Argument of type '{ wrong: string; }' is not assignable to parameter of type '{ foo: string; }'.
// emitter.emit('Connected', { wrong: 'type' })