Вывести сопоставленный тип параметра в машинописи - PullRequest
0 голосов
/ 08 ноября 2019

Я работаю с библиотекой, в которой есть интересное объявление типа (упрощенное):

class Emitter {
    public on<EventName extends 'streamCreated' | 'streamDestroyed'> (eventName: EventName, callback: (event: {
        streamCreated: {id: number};
        streamDestroyed: {reason: string};
    }[EventName]) => void): void;
}

И я пытаюсь напечатать событие в обратном вызове, который я предоставляю:

const callback = (event: StreamDestroyedEvent) => console.log(event.reason);
emitter.on('streamDestroyed', callback);

Однако «StreamDestroyedEvent» не существует. Он не предоставляется библиотекой, а существует только в этом анонимном отображении событий, поэтому вместо этого я попытаюсь сделать вывод:

type InferCallback<T, E> = T extends (eventName: E, event: infer R) => void ? R : never;
type InferCallbackEvent<T, E> = InferCallback<T, E> extends (event: infer P) => void ? P : never;
type StreamDestroyedEvent = InferCallbackEvent<Emitter['on'], 'streamDestroyed'>;

Однако вместо того, чтобы дать мне тип {reason: string}, он получает тип объединения{reason: string;} | {id: number;}. Как я могу получить правильный тип, или это так близко, как я собираюсь получить?

1 Ответ

1 голос
/ 08 ноября 2019

Вы бы хотели "подключить" тип для параметра типа EventName. Хотя TypeScript поддерживает это, когда вызывает обобщенную функцию, он не справляется с работой , представляя такие типы в самой системе типов. Для этого в полной мере потребуется поддержка типов более высокого ранга , которые на самом деле не являются частью языка. И функция emitter.on сама по себе не позволяет вам вызывать ее «частично», так что вы подключаете EventName с первым параметром, а затем компилятор сообщает you , какой тип второгопараметр должен быть.

До выхода TypeScript 3.4 я бы, вероятно, сказал, что получить эту информацию из компилятора было бы невозможно. Я бы попробовал что-то вроде

emitter.on("streamDestroyed", x => {
  type StreamDestroyedEvent = typeof x; // can't get it out of the scope though!
})

, а затем не смог бы получить тип StreamDestroyedEvent для экранирования функции.

В TypeScript 3.4 появилась улучшенная поддержка вывод типа более высокого порядка изуниверсальные функции . Вы по-прежнему не можете представить, что вы делаете, чисто в системе типов, но теперь мы можем определить функцию, которая дает нам то, что мы можем, которая выполняет «частичный» вызов и сохраняет необходимую нам информацию о типе.

Вот функция curry , которая принимает функцию с несколькими аргументами и возвращает новую функцию одного аргумента, которая возвращает другую функцию из оставшихся аргументов:

const curry = <T, U extends any[], R>(
  cb: (t: T, ...args: U) => R
) => (t: T) => (...args: U) => cb(t, ...args);

Если вы вызываете curry на emitter.on, а затем вызываете , что с "streamDestroyed", (в TS3.4 +) вы получаете функцию, которая принимает обратный вызов, который принимает StreamDestroyedEvent, который выможет теперь захватывать:

const streamDestroyedFunc = curry(emitter.on.bind(emitter))("streamDestroyed")
type StreamDestroyedEvent = Parameters<Parameters<typeof streamDestroyedFunc>[0]>[0];
// type StreamDestroyedEvent = { reason: string; }

Обратите внимание, что фактическое поведение во время выполнения вышеупомянутого не является целью;Я пытался убедиться, что это действительно сделало бы что-то разумное, но вы могли бы просто использовать утверждения типа, чтобы лгать компилятору о том, что происходит, если вы не получите ошибку времени выполнения при запуске:

const fakeCurry: typeof curry = () => () => null!;
const fakeOn: Emitter["on"] = null!;
const fakeFunc = fakeCurry(fakeOn)("streamDestroyed");
type SDE2 = Parameters<Parameters<typeof fakeFunc>[0]>[0]; // same thing

Хорошо, надеюсь, это поможет;удачи!

Ссылка на код

...