将枚举值映射到类型 [英] Mapping enum values to types
问题描述
假设我有这样的代码:
// Events we might receive:
enum EventType { PlaySong, SeekTo, StopSong };
// Callbacks we would handle them with:
type PlaySongCallback = (name: string) => void;
type SeekToCallback = (seconds: number) => void;
type StopSongCallback = () => void;
在给出的API中,我可以使用$ $ b注册这样的回调
$ b
In the API I'm given, I can register such a callback with
declare function registerCallback(t: EventType, f: (...args: any[]) => void);
但是我想摆脱任何 any []
,并确保我无法注册错误类型的回调函数。
But I want to get rid of that any[]
and make sure I can't register an ill-typed callback function.
我意识到我可以做到:
type CallbackFor<T extends EventType> =
T extends EventType.PlaySong
? PlaySongCallback
: T extends EventType.SeekTo
? SeekToCallback
: T extends EventType.StopSong
? StopSongCallback
: never;
declare function registerCallback<T extends EventType>(t: T, f: CallbackFor<T>);
// Rendering this valid:
registerCallback(EventType.PlaySong, (name: string) => { /* ... */ })
// But these invalid:
// registerCallback(EventType.PlaySong, (x: boolean) => { /* ... */ })
// registerCallback(EventType.SeekTo, (name: string) => { /* ... */ })
这真的很漂亮,强大!感觉就像我在使用依赖类型:我基本上是在这里写了一个将值映射到类型的函数。
This is really nifty and powerful! It feels like I'm using dependent types: I basically wrote myself a function mapping values to types, here.
但是,我不知道TypeScript的全部功能类型系统,也许还有更好的方法将枚举值映射到这样的类型。
However, I don't know the full strength of TypeScript's type system and maybe there is an even better way to map enum values to types like this.
是否有更好的方法将枚举值映射到这样的类型?是否可以避免上述很大的条件类型? (实际上,我发生了很多事件,这简直是一团糟:当我将鼠标悬停在 CallbackFor
上时,VS Code表现出了巨大的表情,而我的棉绒呢真的想在每次:
。)
Is there a better way to map enum values to types like this? Can I avoid a really big conditional type as above? (In reality I have many events, and it's kind of a mess: VS Code shows a huge expression when I hover over CallbackFor
, and my linter really wants to indent after every :
.)
我很想编写一个将枚举值映射为类型的对象,因此可以声明 registerCallback
使用 T
和 CallbackFor [T]
,但这不是似乎是一回事。
I'd love to write an object mapping enum values to types, so I can declare registerCallback
using T
and CallbackFor[T]
, but that doesn't seem to be a thing. Any insights are appreciated!
推荐答案
我们可以创建一个在枚举成员和回调类型之间映射的类型,但是如果直接在 registerCallback
中使用它,我们将无法正确推断出回调参数类型:
We can create a type that maps between the enum members and the callback types, but the if we use it directly in registerCallback
we will not get correct inference for callback argument types:
type EventTypeCallbackMap = {
[EventType.PlaySong] : PlaySongCallback,
[EventType.SeekTo] : SeekToCallback,
[EventType.StopSong] : StopSongCallback,
}
declare function registerCallback
<T extends EventType>(t: T, f: EventTypeCallbackMap[T]): void;
registerCallback(EventType.PlaySong, n => { }) // n is any
如果只有3个事件类型,则多个重载实际上是一个很好的解决方案:
If you have just 3 event types, multiple overloads are actually a pretty good solution:
declare function registerCallback(t: EventType.PlaySong, f: PlaySongCallback): void;
declare function registerCallback(t: EventType.SeekTo, f: SeekToCallback): void;
declare function registerCallback(t: EventType.StopSong, f: StopSongCallback): void;
registerCallback(EventType.PlaySong, n => { }) // n is string
如果您有很多枚举成员,您还可以自动生成重载签名:
If you have a lot of enum member you could also generate the overload signature automatically:
type EventTypeCallbackMap = {
[EventType.PlaySong]: PlaySongCallback,
[EventType.SeekTo]: SeekToCallback,
[EventType.StopSong]: StopSongCallback,
}
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
declare let registerCallback: UnionToIntersection<
EventType extends infer T ?
T extends T ? (t: T, f: EventTypeCallbackMap[T]) => void :
never: never
>
registerCallback(EventType.PlaySong, n => { }) // n is string
请参见此处(并进行投票)答案)以解释 UnionToIntersection
See here (and up-vote the answer) for an explanation of UnionToIntersection
这篇关于将枚举值映射到类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!