Skip to main content

Conditional generics

Конструкція вигляду T extends U ? X : Y в TypeScript називається умовним типом (conditional type).

Вона працює подібним чином до оператора ?: у JavaScript, але застосовується на рівні типів, а не значень.


Загальна форма

javascript
T extends U ? X : Y

Можна прочитати як:

"Якщо тип T сумісний (assignable) з типом U, то результатом буде тип X, інакше - тип Y."


Приклад 1: Базовий приклад

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

Якщо T можна присвоїти типу string, повертається "yes", інакше "no".


Приклад 2: Перевірка підтипу

javascript
type ExtendsExample<T> = T extends number ? number : string; let a: ExtendsExample<42>; // number let b: ExtendsExample<"foo">; // string

Тут TypeScript перевіряє: чи є T підтипом number - якщо так, повертає number.


Приклад 3: Складний тип з об'єктами

javascript
type HasId<T> = T extends { id: any } ? "Has ID" : "No ID"; type A = HasId<{ id: number; name: string }>; // "Has ID" type B = HasId<{ name: string }>; // "No ID"

Працює навіть зі структурами об'єктів (структурна перевірка типів).


Приклад 4: Вилучення типу з infer

Умовні типи часто комбінують з infer, щоб вилучати вкладені типи:

javascript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T; type A = UnwrapPromise<Promise<string>>; // string type B = UnwrapPromise<number>; // number

"Якщо T - це Promise<X>, повертаємо X, інакше - сам T."


Приклад 5: Фільтрація типів

javascript
type ExcludeString<T> = T extends string ? never : T; type Result = ExcludeString<string | number | boolean>; // number | boolean

Тут TypeScript застосовує умову до кожного елемента union-типу (string, number, boolean), а потім об'єднує результати - це називається дистрибутивна поведінка умовних типів.


Приклад 6: Зворотний варіант - вилучення лише рядків

javascript
type ExtractString<T> = T extends string ? T : never; type A = ExtractString<string | number | boolean>; // string

Приклад 7: Застосування з keyof

javascript
type PropertyExists<T, K> = K extends keyof T ? true : false; type Obj = { id: number; name: string }; type A = PropertyExists<Obj, "id">; // true type B = PropertyExists<Obj, "age">; // false

Перевіряємо, чи є конкретний ключ K в об'єкті T.


Приклад 8: Порівняння з "if" на рівні значень

JS:

javascript
const result = condition ? value1 : value2;

Тип TS:

javascript
type Result<T> = T extends true ? "yes" : "no";

Працює під час компіляції, а не під час виконання коду.


Як це працює всередині

  • T extends U - перевірка сумісності типів (а не успадкування).

  • Якщо T - union (наприклад A | B | C), умовний тип застосовується до кожного члена об'єднання окремо:

    javascript
    (A | B) extends U ? X : Y -> (A extends U ? X : Y) | (B extends U ? X : Y)

Це називається дистрибутивна поведінка умовних типів.


Приклад 9: Недистрибутивний варіант

Щоб вимкнути "розподіл" по union-типу, T обгортають у кортеж [...]:

javascript
type NonDistributive<T> = [T] extends [string] ? "yes" : "no"; type A = NonDistributive<string | number>; // "no"

Без дужок результат був би "yes" | "no".


Приклад 10: Реалізація вбудованих утиліт через умовні типи

УтилітаРеалізаціяОпис
Exclude<T, U>T extends U ? never : TПрибирає з T усі типи, сумісні з U
Extract<T, U>T extends U ? T : neverЗалишає лише ті типи з T, що сумісні з U
NonNullable<T>`T extends nullundefined ? never : T`
ReturnType<T>T extends (...args: any[]) => infer R ? R : anyВилучає тип значення, що повертається функцією

Підсумок

ЕлементЗначення
СинтаксисT extends U ? X : Y
Сенс"Якщо T підтип U, то X, інакше Y"
Застосовується дотипів, а не значень
Особливістьпрацює "дистрибутивно" з union-типами
Часто використовується зinfer, Extract, Exclude, NonNullable, ReturnType

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.