strongly typed Map Entries typescript

huangapple go评论63阅读模式
英文:

strongly typed Map Entries typescript

问题

以下是您提供的内容的中文翻译:

我正在尝试以强类型的方式在Typescript中创建一个事件到其对应事件处理程序的映射。

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

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

// 帮我定义EventToHandlerMap的类型

// 我希望这个能通过
const map: EventToHandlerMap = new Map([
  [EventA, handleA],
  [EventB, handleB]
]);

// 我希望这个不能通过
const map: EventToHandlerMap = new Map([
  [EventA, handleB],
  [EventB, handleA]
]);

是否有一种方式可以在Map的每个条目中建模键和值之间的关系?

英文:

I'm trying to create a map in Typescript of events to their corresponding event handlers in a strongly typed way.

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]
]);

Is there a way to model a relationship between the key and value in every entry of the Map

答案1

得分: 1

这类似于https://stackoverflow.com/q/54907009/2887218,这里的答案与我在那里的答案类似,但它们有足够的不同,以至于这个问题和答案不是直接重复。


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使得键和值相关联。

然后,我们需要以类似的方式定义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的别名,并断言它表现为EventToHandlerMapConstructor。这种断言是必要的,因为编译器不知道Map会以这种方式工作。

一旦我们这样做了,你可以将EventToHandlerMap用作你的构造函数,并获得所需的行为:

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

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

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

[在 Playground 上查看代码](https://www.typescriptlang.org/play?#code/MYGwhgzhAECiBuBTAdgFwILUQD1SgJjAiqtAN4BQ00Y0AvNAIwUC+FokRSaAQlrgS4lyVaACN60AEysKFAGYBXZMFQBLAPbJoACzDJ8IROgAUibhgBccC+gCU1+BrX4R1YFogajAOhAaAczNbHzA7WSUVdS1dfUNEHmCSHmtiXgdoJxc3aA9kL19-IPNknzFwtgpUAE8AB0QbEgBhVA0AJwAeABV+PAMhNAA+SWREAHdoEx9psDaAiGt9art6Ya6Abiq6hoAJOKNOnpw+wkahySS0ay6VumGs-E21NEQ2+TBgBrTULo09gwOAFkwLUcvJ2rAPjoTMAwCAQGIPgBreTIazdXqCM6oQYmUTUeBwxSIaz-eKHQYAGmgSMQ1VSFha7W6VOgAFsQQySL8yUCQaJbvdnPhqagdGoIOg5gB+RbIZaOYWbagBRCoDHHLHfXG0+nYpkUjK814s6AAH2gynwiHkz0Qj1EEDVGoE-WxOrpXLQBpZ1MJIGJpP2Jq6gwyYolyugbUQYHwWhA1WgEDUAC8SdBkIo2WJXptKs88G8Pl8LDzg21gbUmp5UG1FKp2jlRhMXScBqgANoAXVx+KwaDaakQC2gnemPjIY4AktBnjS6Rp5NAut3rJ2kV7UD6up3p72-UGASG973u9AWN2BVvy8fKyCoy2TBlvrfyVWozG4wmk7U2hpWhqeobz+CsP1kdha2xN8+VBBgqxoDsYNeKsa3yOsG1aNpNjkSD0PZTloNAu8EIYAB6MjoA0JEwGqUQWyI4171qExO07b50GpPRj3QbtqXYiweC44MeG7bs7BwvIIFIDlaikECmNI+jxkYsCQVYgSSE42Jj1E-jviEnT4l48T1mgCisDaf82lECzqHshzHKc5zqAAP3cjzXOoDoAFo-P8nzhk8jzbMorptmgAByS5

英文:

This is similar to https://stackoverflow.com/q/54907009/2887218 and the answer here is similar to my answer there, but they are different enough that this question and answer isn't a direct duplicate.


The TypeScript typings for Map&lt;K, V&gt; don't reflect any more specific relationship between keys and values than "the keys are of type K and the values are of type V". If you need something more specific, you'll need to write your own typings for it. That involves looking at the library files and modifying them.

In your case you want each key to be an Event constructor like

type EventCtor&lt;T extends Event&gt; = new (...args: any) =&gt; T;

and each value to be a handler for the corresponding event like

type Handler&lt;T extends Event&gt; = (event: T) =&gt; void;

So we will need to write an EventToHandlerMap that behaves like some more specific version of EventToHandlerMap&lt;EventCtor&lt;any&gt;, Handler&lt;any&gt;&gt;:

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

That hopefully works how you need it; get() and set() for example are generic in T extends Event so that the key and value are related.

Then we'll need to define the Map constructor in a similar way, so that the compiler knows how to interpret the constructor arguments:

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

const EventToHandlerMap = Map as EventToHandlerMapConstructor;

Note how I've just made EventToHandlerMap an alias of Map and asserted that it behaves as an EventToHandlerMapConstructor. That assertion is necessary because the compiler doesn't know that Map will work that way.

Once we do that, you can use EventToHandlerMap as your constructor and get the desired behavior:

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

const map2: EventToHandlerMap =
  new EventToHandlerMap([[EventA, handleB], [EventB, handleA]]); // error
  //                              ~~~~~~~  &lt;-------&gt; ~~~~~~~
  // Type &#39;(eventB: EventB) =&gt; void&#39; is not assignable to type &#39;Handler&lt;EventA&gt;&#39;.
  // Type &#39;(eventA: EventA) =&gt; void&#39; is not assignable to type &#39;Handler&lt;EventB&gt;&#39;.

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

Playground link to code

huangapple
  • 本文由 发表于 2023年5月30日 04:38:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360218.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定