Infer в TypeScript: пример использования

Infer в TypeScript: пример использования

FurryCat 😼

Мой первый самостоятельный кейс использования ключевого слова infer. Не могу не поделиться :)

Есть условная функция fetchAndFormat, которая получает данные из api и форматирует их. У нее два аргумента: fetcherFn и formatterFn. Причем второй - необязательный, если его нет, нужно вернуть оригинальные неотформатированные данные.

type FetcherFn = () => Promise<any>;
type FormatterFn = (data: any) => any;

async function fetchAndFormat(fetcherFn: FetcherFn, formatterFn?: FormatterFn) {
  const data = await fetcherFn();
  if (typeof formatterFn === 'function') {
    return formatterFn(data);
  }
  return data;
}

// использование
type FetchedType = {
  id: number;
  name: string;
};
type FormattedType = {
  id: string;
  text: string;
};
function fetchData(): Promise<FetchedType> {
  return Promise.resolve({ id: 234, name: "Product name" });
}
function formatData(data: FetchedType): FormattedType {
  return { id: `${data.id}`, text: data.name };
}

fetchAndFormat(fetchData, formatData).then(result => {});
fetchAndFormat(fetchData).then(result => {});

Задача состоит в том, чтобы определить тип переменной result.

Тип данных не определяется

С какими типами данных работают fetcherFn и formatterFn неизвестно и передавать при каждом вызове типы в виде дженериков совсем не хочется. Поэтому попробуем вывести их автоматически внутри fetchAndFormat.

Введем два обобщенных типа T и K для аргументов функции:

async function fetchAndFormat<T extends FetcherFn, K>(fetcherFn: T, formatterFn?: K) {
  const data = await fetcherFn();
  if (typeof formatterFn === 'function') {
    return formatterFn(data);
  }
  return data;
}

Для T можно сразу уточнить тип - extends FetcherFn. Для K у меня так сделать не получилось, потому что аргумент необязательный и дальше возникли проблемы с определением типа :(

Теперь постараемся вывести нужные нам типы из полученных с помощью infer:

type UnpackFetcherResult<T> = 
  T extends (...params: any[]) => Promise<infer K> 
  ? K : any; 

type UnpackFormatterResult<T> = 
  T extends (data: any) => infer K 
  ? K : any;

type UnpackResult<T, K> = K extends FormatterFn ? 
  UnpackFormatterResult<K> :
  UnpackFetcherResult<T>;

Если тип функции соответствует тому, что мы ожидаем, мы сможем получить тип данных, которые эта функция возвращает. В ином случае будет просто any.

Осталось только добавить возвращаемый тип в сигнатуру fetchAndFormat:

async function fetchAndFormat<T extends FetcherFn, K>(fetcherFn: T, formatterFn?: K): UnpackResult<T,K> {
  //...
}

Теперь тип для возвращаемого значения определяется правильно:

При передаче formatterFn выводится тип, который возвращает форматтер
Если не передавать formatterFn, выводится тип, который возвращает фетчер


Report Page