当前正在尝试实现类型化事件系统,但我遇到以下问题:
enum Event {
ItemCreated = "item-created",
UserUpdated = "user-updated",
}
export interface Events {
[Event.ItemCreated]: (item: string) => void;
[Event.UserUpdated]: (user: number) => void;
}
export interface EventListener<TEvent extends keyof Events = keyof Events> {
event: TEvent;
handler: (...args: Parameters<Events[TEvent]>) => void;
}
const UserListener: EventListener = {
event: Event.UserUpdated,
handler: (handlerArgs) => {
console.log(handlerArgs);
},
};
不幸的是,handlerArgs
的类型是 string | User
- 但我希望它只是 User
。我在这里缺少什么?
最佳答案
问题
interface EventListener<TEvent extends keyof Events = keyof Events> {
event: TEvent;
handler: (...args: Parameters<Events[TEvent]>) => void;
}
是当您将类型称为 EventListener
时如果没有类型参数,则类型参数被推断为 the default keyof Events
,这是 union type 。然后都是event
和handler
是根据该联合编写的,彼此独立。
你真的想要EventListener<TEvent>
分配 TEvent
中的联合体,这样 EventListener<X | Y>
将相当于 EventListener<X> | EventListener<Y>
。因此EventListener
(没有类型参数)本身应该是一个联合。 (由于 interface
types 不能是并集,因此您需要将其更改为 type
alias。)
有多种写法。您可以使用 distributive conditional type强制联合输入产生联合输出:
type EventListener<TEvent extends keyof Events = keyof Events> =
TEvent extends any ? {
event: TEvent;
handler: (...args: Parameters<Events[TEvent]>) => void;
} : never;
type E = EventListener
/* type E = {
event: Event.ItemCreated;
handler: (item: string) => void;
} | {
event: Event.UserUpdated;
handler: (user: number) => void;
} */
但似乎您甚至可能不需要指定类型参数,在这种情况下您可以只定义 EventListener
作为特定的联合类型。如果是这样,您可以重构为分布式对象类型(如 microsoft/TypeScript#47109 中所创造的),其中 index into一个mapped type以获得所需的联盟。像这样:
type EventListener = { [TEvent in keyof Events]: {
event: TEvent,
handler: (...args: Parameters<Events[TEvent]>) => void
} }[keyof Events];
/* type EventListener = {
event: Event.ItemCreated;
handler: (item: string) => void;
} | {
event: Event.UserUpdated;
handler: (user: number) => void;
} */
无论如何,EventListener
现在是discriminated union编译器将使用 event
作为判别属性来缩小 handler
的类型所需的属性:
const UserListener: EventListener = {
event: Event.UserUpdated,
handler: (handlerArgs) => {
handlerArgs.toFixed(2); // okay
console.log(handlerArgs);
},
};
关于typescript - 类型化事件系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75932281/