Conditional Types в глубину
Conditional Types
Синтаксис и основы
// T extends U ? X : Y
type IsString<T> = T extends string ? 'да' : 'нет'
type A = IsString<string> // 'да'
type B = IsString<number> // 'нет'
type C = IsString<'hello'> // 'да' — literal string extends string
Дистрибутивные условные типы
Когда параметр типа — «голый» T (не обёрнут в структуру), условный тип дистрибутивен:
// NonNullable — встроенный тип
type NonNullable<T> = T extends null | undefined ? never : T
type A = NonNullable<string | null | undefined>
// Распределяется:
// (string extends null|undefined ? never : string) |
// (null extends null|undefined ? never : null) |
// (undefined extends null|undefined ? never : undefined)
// = string | never | never = string
// Извлечение из union
type Extract<T, U> = T extends U ? T : never
type Numbers = Extract<string | number | boolean, number>
// = number
type Exclude<T, U> = T extends U ? never : T
type NonNumbers = Exclude<string | number | boolean, number>
// = string | boolean
Дистрибутивность работает только когда параметр типа используется как
T, не как T[], [T] или { val: T }. Чтобы отключить дистрибутивность — оберните в кортеж: [T] extends [U].infer в условных типах
// Типичные паттерны с infer
// Тип возвращаемого значения функции
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
// Тип первого аргумента
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never
// Тип элемента массива
type ElementType<T> = T extends (infer E)[] ? E : never
// Распаковка Promise (рекурсивная)
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
// Тип ключа Record
type KeyType<T> = T extends Record<infer K, any> ? K : never
Рекурсивные условные типы
// Flatten вложенных массивов
type Flatten<T> = T extends Array<infer Item>
? Flatten<Item>
: T
type Result = Flatten<number[][][]> // number
// Deeply remove null/undefined
type DeepNonNullable<T> = T extends null | undefined
? never
: T extends object
? { [K in keyof T]: DeepNonNullable<T[K]> }
: T
TypeScript позволяет создавать сложные type-level алгоритмы:
// Создаём тип-кортеж из N элементов типа T
type Tuple<T, N extends number, R extends unknown[] = []> =
R['length'] extends N ? R : Tuple<T, N, [T, ...R]>
type Triple = Tuple<string, 3> // [string, string, string]
// Вычисляем длину кортежа
type Length<T extends any[]> = T['length']
type Len = Length<[1, 2, 3]> // 3
// Сложение через кортежи
type Add<A extends number, B extends number> =
Length<[...Tuple<0, A>, ...Tuple<0, B>]>
type Sum = Add<3, 4> // 7
Это type-level программирование — TypeScript становится Turing-complete языком на уровне типов!