ПрограммированиеTypeScript архитектор

Как работает conditional types (условные типы) в TypeScript? Что такое дистрибутивное поведение условных типов и как с ним работают? Поясните на примерах.

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

Ответ.

Conditional Types (условные типы) в TypeScript позволяют описывать один тип через другой по принципу T extends U ? X : Y. Такие типы дают мощные возможности при построении сложной типовой логики, особенно в библиотеках и декларациях API.

Пример базового условного типа:

type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<string>; // 'yes' type B = IsString<number>; // 'no'

Дистрибутивное поведение

Если условный тип применяется к union-типу, то TypeScript "распределяет" условие по каждому элементу union отдельно. Это называется дистрибутивным поведением условных типов.

Пример:

type Foo<T> = T extends { id: number } ? string : boolean; type Result = Foo<{id: number} | {name: string}>; // string | boolean

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

Как избежать дистрибутивного поведения

Оборачиваем тип в кортеж:

type NoDistrib<T> = [T] extends [{id: number}] ? string : boolean; type Result = NoDistrib<{id: number} | {name: string}>; // boolean

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

Вопрос: "Что произойдёт, если использовать условный тип с union-типами, не обернув в кортежи? Всегда ли результат — тот же, что при обычной логике?"

Ответ: Результат может быть неожиданным! Из-за дистрибутивности условие применяется к каждому члену union-типов отдельно. Чтобы строго сравнить весь union-тип, нужно использовать обертку (tuple).

Пример:

type Test<T> = T extends string ? number : boolean; type A = Test<string | boolean>; // number | boolean, а не просто boolean

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


История

В библиотеке для сериализации использовали условный тип для проверки структуры данных, но забыли обернуть дженерик-параметр в tuple. В результате на сложных union-типах декларации ломались, а компилятор выдавал непредсказуемые типы при использовании API.


История

При попытке реализовать типовую трансформацию для обработки полей моделей возникла потеря части информации: из-за того, что дистрибутивность порождает объединения, пару ветвей логики забыли обработать, и в итоге типизация стала слишком permissive.


История

Разработчик полагал, что T extends SomeType внутри conditional types поведет себя так, как мы ожидаем для всего объекта, но случилось "распыление" — компилятор указал на несогласованность, и начались тайп-хаос и серьёзные баги при генерации автодокументации.