英文:
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
英文:
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<K, V>
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<T extends Event> = new (...args: any) => T;
and each value to be a handler for the corresponding event like
type Handler<T extends Event> = (event: T) => void;
So we will need to write an EventToHandlerMap
that behaves like some more specific version of 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;
}
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 <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;
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
// ~~~~~~~ <-------> ~~~~~~~
// 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论