typescript 强类型Map条目类型脚本

3phpmpom  于 2023-06-07  发布在  TypeScript
关注(0)|答案(1)|浏览(180)

我试图以强类型的方式在Typescript中创建一个事件到其相应事件处理程序的Map。

class EventA extends Event {}
class EventB extends Event {}

function handleA(eventA: EventA): void {}
function handleB(eventB: EventB): void {}

// help me with typing EventToHandlerMap

// I want this to pass
const map: EventToHandlerMap = new Map([
  [EventA, handleA],
  [EventB, handleB]
]);

// I want this to fail
const map: EventToHandlerMap = new Map([
  [EventA, handleB],
  [EventB, handleA]
]);

有没有一种方法可以在Map的每个条目中对键和值之间的关系进行建模

qyuhtwio

qyuhtwio1#

这与Typescript: How can I make entries in an ES6 Map based on an object key/value type类似,这里的答案与我在那里的答案类似,但它们有足够的不同,因此这个问题和答案不是直接重复的。
Map<K, V>的TypeScript类型并没有反映键和值之间的任何更具体的关系,而不是“键的类型是K,值的类型是V”。如果你需要更具体的东西,你需要自己写打字。这包括查看库文件并修改它们。
在本例中,您希望每个键都是一个Event构造函数,如

type EventCtor<T extends Event> = new (...args: any) => T;

并且每个值都是相应事件的处理程序,如

type Handler<T extends Event> = (event: T) => void;

因此,我们需要编写一个EventToHandlerMap,它的行为类似于EventToHandlerMap<EventCtor<any>, Handler<any>>的一些更具体的版本:

interface EventToHandlerMap {
  forEach(callbackfn: <T extends Event>(
    value: Handler<T>, key: EventCtor<T>, map: EventToHandlerMap
  ) => void, thisArg?: any): void;
  get<T extends Event>(key: EventCtor<T>): Handler<T> | undefined;
  set<T extends Event>(key: EventCtor<T>, value: Handler<T>): this;
  readonly size: number;
}

希望能按你的需要工作;例如,get()set()T extends Event中的generic,因此键和值是相关的。
然后我们需要以类似的方式定义Map构造函数,以便编译器知道如何解释构造函数参数:

interface EventToHandlerMapConstructor {
  new <T extends Event[]>(
    entries: [...{ [I in keyof T]: [k: EventCtor<T[I]>, v: Handler<T[I]>] }]
  ): EventToHandlerMap;
  new(): EventToHandlerMap;
  readonly prototype: EventToHandlerMap;
}

const EventToHandlerMap = Map as EventToHandlerMapConstructor;

请注意,我刚刚将EventToHandlerMap作为Map的别名,并Assert它的行为与EventToHandlerMapConstructor相同。这个Assert是必要的,因为编译器不知道Map将以这种方式工作。
一旦我们这样做了,你就可以使用EventToHandlerMap作为你的构造函数,并获得所需的行为:

const map: EventToHandlerMap = // okay
  new EventToHandlerMap([[EventA, handleA], [EventB, handleB]]);

const map2: EventToHandlerMap =
  new EventToHandlerMap([[EventA, handleB], [EventB, handleA]]); // error
  //                              ~~~~~~~  <-------> ~~~~~~~
  // Type '(eventB: EventB) => void' is not assignable to type 'Handler<EventA>'.
  // Type '(eventA: EventA) => void' is not assignable to type 'Handler<EventB>'.

map.get(EventA)?.(new EventA("")); // okay
map.get(EventB)?.(new EventB("")); // okay

Playground链接到代码

相关问题