typescript - 类型化事件系统

标签 typescript

当前正在尝试实现类型化事件系统,但我遇到以下问题:

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 。然后都是eventhandler是根据该联合编写的,彼此独立。

你真的想要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);
  },
};

Playground link to code

关于typescript - 类型化事件系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75932281/

相关文章:

reactjs - 使用 Typescript 的 React-Select 导入失败

typescript 枚举默认值

typescript - 检查字符串是否包含在 Typescript 的 ReadonlyArray 中

typescript - 使用 typeof 作为参数类型

javascript - 检查 Protractor 中是否显示了 2 个元素中的 1 个

reactjs - 如何避免在 redux thunk 中使用单独的 _PENDING _FULFILLED 和 _REJECTED 操作?

angular - 在调整窗口大小之前,我无法正确显示 openlayer map

typescript - 在哪里可以找到有关 Typescript 支持政策的文档?

typescript - TypeScript 可以通过 "extracted" bool 逻辑推断可区分联合的类型吗?

javascript - TypeScript 编译上下文中包含 app.component.ts 的错误