确保记录键匹配的TypeScript泛型

but5z9lq  于 2023-01-31  发布在  TypeScript
关注(0)|答案(1)|浏览(110)

我正在尝试编写一个class,它将为在interface中指定并作为generic传递的每个key + value提供类型安全。
我已经设法让一切工作,除了我无法确保key属于interface。换句话说:我可以将在提供给genericinterface中找不到的string值传递给generic
我只是不知道如何调整我的代码,以便我可以保证key存在于interface中。
代码的超精简版本:

type Listener<V = unknown[]> = (...values: V[]) => void;
type EventMap = Record<string, Listener>;

type Library<T> = Partial<Record<keyof T, Set<T[keyof T]>>>;

class EventBlaster<T extends EventMap> {
  lib: Library<T> = {};

  register<K extends keyof T>(eventName: K, listener: T[K]) {}
  trigger<K extends keyof T>(eventName: K, ...values: Parameters<T[K]>) {}
}

///
/// Usage

type CustomMap = EventMap & {
  change(value: string): void;
};

const customEvents = new EventBlaster<CustomMap>();

// Working register / trigger

customEvents.register('change', (value) => {});
customEvents.trigger('change', 'hello');

// TypeScript correctly throwing errors on wrong listener arguments

customEvents.trigger('change', '1st string', '2nd string');

// TypeScript failing to throw errors on undeclared event names

customEvents.register('nope', () => {});
customEvents.trigger('nope');

在这个例子中,我已经让所有的listener > argument values按预期工作了--这很好,我认为这是最困难的部分......
但不幸的是,您可以看到,我可以使用string值调用register / trigger,而这些值不是CustomMap类型上的keys
如果我不用每次定义interface时都使用extend EventMap作为EventBlastergeneric参数,那就太好了...但我的假设是,这是获得我所追求的行为的必要条件。
在这个TypeScript Playground链接中可以找到一个稍微健壮一些的代码示例。

hwazgwia

hwazgwia1#

在编写的代码中,任何任意字符串都是CustomMap中的有效键。
CustomMap的类型是Record<string, Listener> & { change(value: string): void; },看起来你认为这个类型等价于{ change(value: string): void; },但事实并非如此,交集类型说,这个值必须同时符合A和B的类型,这意味着任何符合Record<string, Listener>{ change(value: string): void;}的值,所以任何类型,只要有监听器作为值,字符串作为键,并且具有特定的签名,密钥更改将符合类型。结果是CustomMap支持任意字符串键控的侦听器值属性,只要它也将charge作为属性。
总的来说,我看到很多人严重误解了Record<string, X>的含义。Record<string, X>是任意字符串键到值的Map。使用它告诉编译器接受任何属性名。我也看到很多人似乎并不真正理解交集的作用。它们不是继承。特别是,你不需要使用交集来强制泛型类型中的“扩展”。
Typescript是结构类型化的。所以类型A需要做的是“扩展”类型B,使其符合它。你永远不需要显式地将两者联系起来。
类型{change(value: string): void;}已经扩展了EventMap,因为它具有值为Listeners的所有字符串键。

相关问题