The problem
Suppose I have some code like this:
// 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;
In the API I'm given, I can register such a callback with
declare function registerCallback(t: EventType, f: (...args: any[]) => void);
But I want to get rid of that any[]
and make sure I can't register an ill-typed callback function.
A solution?
I realized I can do this:
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.
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.
The question
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 :
.)
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!