将枚举值映射到类型

Lyn*_*ynn 2 enums types dependent-type typescript conditional-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;
Run Code Online (Sandbox Code Playgroud)

在我给出的 API 中,我可以注册这样的回调

declare function registerCallback(t: EventType, f: (...args: any[]) => void);
Run Code Online (Sandbox Code Playgroud)

但我想摆脱它any[]并确保我不能注册错误类型的回调函数。

一个办法?

我意识到我可以这样做:

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) => { /* ... */ })
Run Code Online (Sandbox Code Playgroud)

这真的很漂亮和强大!感觉就像我在使用依赖类型:我基本上在这里写了一个将值映射到类型的函数。

但是,我不知道 TypeScript 类型系统的全部实力,也许有更好的方法将枚举值映射到这样的类型。

问题

有没有更好的方法将枚举值映射到这样的类型?我可以避免如上所述的非常大的条件类型吗?(实际上我有很多事件,而且有点混乱:当我将鼠标悬停在 VS Code 上时,它会显示一个巨大的表达式CallbackFor,并且我的 linter 真的想在每次之后缩进:.。)

我很想写一个将枚举值映射到类型的对象,所以我可以声明registerCallback使用Tand CallbackFor[T],但这似乎不是一回事。任何见解表示赞赏!

Tit*_*mir 6

我们可以创建一个在枚举成员和回调类型之间映射的类型,但是如果我们直接使用它,registerCallback我们将无法获得回调参数类型的正确推断:

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
Run Code Online (Sandbox Code Playgroud)

如果您只有 3 种事件类型,多重重载实际上是一个不错的解决方案:

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
Run Code Online (Sandbox Code Playgroud)

如果您有很多 enum 成员,您还可以自动生成重载签名:

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
Run Code Online (Sandbox Code Playgroud)

请参阅此处(并对答案投赞成票)以了解有关UnionToIntersection