Отображение значений перечисления на типы - PullRequest
0 голосов
/ 06 ноября 2018

проблема

Предположим, у меня есть такой код:

// 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, который мне дан, я могу зарегистрировать такой обратный вызов с помощью

declare function registerCallback(t: EventType, f: (...args: any[]) => void);

Но я хочу избавиться от этого 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) => { /* ... */ })

Это действительно изящно и мощно! Такое ощущение, что я использую зависимые типы: я в основном написал себе функцию, отображающую значения в типы, здесь.

Однако я не знаю всей силы системы типов TypeScript и, возможно, есть еще лучший способ сопоставления значений перечисления с такими типами.

Вопрос

Есть ли лучший способ сопоставить значения перечисления с такими типами? Можно ли избежать действительно большого условного типа, как указано выше? (На самом деле у меня много событий, и это своего рода беспорядок: VS Code показывает огромное выражение, когда я наводю курсор на CallbackFor, и мой линтер действительно хочет делать отступ после каждого :.)

Я бы хотел записать значения перечисления объектного сопоставления в типы, чтобы я мог объявить registerCallback, используя T и CallbackFor[T], но это не похоже на вещь. Любые идеи приветствуются!

Ответы [ 2 ]

0 голосов
/ 06 ноября 2018

Мы можем создать тип, который отображается между членами enum и типами обратного вызова, но если мы используем его непосредственно в 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

Если у вас есть только 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

Если у вас много членов 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 any ? (t: T, f: EventTypeCallbackMap[T]) => void :
    never: never
> 


registerCallback(EventType.PlaySong, n => { }) // n is string

См. здесь (и проголосуйте с ответом) для объяснения UnionToIntersection

0 голосов
/ 06 ноября 2018

Вместо настройки сложного сопоставления рассмотрите возможность использования объявлений переопределения:

declare function registerCallback(t: EventType.PlaySong, f: PlaySongCallback);
declare function registerCallback(t: EventType.SeekTo, f: SeekToCallback);
declare function registerCallback(t: EventType.StopSong, f: StopSongCallback);

Я считаю, что это гораздо удобнее для чтения и сопровождения, чем настройка явного типа отображения, хотя я понимаю неудобство, связанное с отсутствием единой общей сигнатуры. Следует помнить одну вещь: люди, использующие ваш API, определенно предпочтут прозрачность объявлений переопределения непрозрачному типу CallbackFor<T>, который на самом деле не говорит само за себя.

Попробуйте это на TypeScript Playground , и не забудьте указать тип возврата для registerCallback(), если у вас установлен флаг noImplicitAny.

...