ПрограммированиеFrontend разработчик

Что такое распределённые типы (Distributive Types) в TypeScript, где они применяются и какие особенности стоит учитывать при работе с ними?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Распределённые типы (Distributive Types) — это особенность TypeScript, проявляющаяся при работе с условными типами (T extends U ? X : Y). Когда переменная-тип слева от extends является объединением (union), TypeScript распределяет условие по каждому элементу объединения.

Пример:

// "Test<A> | Test<B> | Test<C>" type Test<T> = T extends string ? () => string : () => number; type Result = Test<'A' | 'B' | 'C'>;

Здесь Result распределится как:

  • Test<'A'> | Test<'B'> | Test<'C'>

Когда применяют — для написания универсальных API, манипуляций над объединениями, например, для фильтрации или сопоставления с образцом.

Особенности:

  • Если обернуть T в кортеж [T], распределение не произойдёт. Это способ "отключить" распределённость.
  • Распределение работает только для типовых переменных (а не литералов).

Вопрос с подвохом

Вопрос: Будет ли тип T[] extends number[] ? true : false распределённым?

Правильный ответ: Нет, распределённость происходит только если слева переменная-тип без обёртки в массив или кортеж. Например, условный тип T extends number ? ... будет распределять, но T[] extends number[] ? ... — нет.


Примеры реальных ошибок из-за незнания тонкостей темы


История

Проект: Библиотека валидации props для React-компонентов. Хотели реализовать тип, превращающий union-пропсы в строгий интерфейс, но из-за незнания распределения тип стал неожиданно сложным (смешались свойства членов union). Исправили, добавив обёртку [T], чтобы распределение не происходило.


История

Проект: Разрабатывали помещение всех типов события в одну функцию-обработчик. В условном типе ожидалось, что функция будет принимать каждый тип события через распределение, но без явного его использования реализовали обработку некорректно, что привело к ошибочным типам аргументов (union вместо отдельного вызова для каждого типа).


История

Проект: При создании собственных утилит-типов (типа Exclude, Extract) забыли, что распределение не произойдёт для кортежей и массивов. В результате тип Exclude не работал для массивов (например, тип Exclude<["a"|"b"], "b"> не удалял "b").