Conditional generics
Конструкція вигляду T extends U ? X : Y в TypeScript називається
умовним типом (conditional type).
Вона працює подібним чином до оператора ?: у JavaScript,
але застосовується на рівні типів, а не значень.
Загальна форма
T extends U ? X : YМожна прочитати як:
"Якщо тип
Tсумісний (assignable) з типомU, то результатом буде типX, інакше - типY."
Приклад 1: Базовий приклад
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"Якщо
Tможна присвоїти типуstring, повертається"yes", інакше"no".
Приклад 2: Перевірка підтипу
type ExtendsExample<T> = T extends number ? number : string;
let a: ExtendsExample<42>; // number
let b: ExtendsExample<"foo">; // stringТут TypeScript перевіряє:
чи є T підтипом number - якщо так, повертає number.
Приклад 3: Складний тип з об'єктами
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, щоб вилучати вкладені типи:
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: Фільтрація типів
type ExcludeString<T> = T extends string ? never : T;
type Result = ExcludeString<string | number | boolean>;
// number | booleanТут TypeScript застосовує умову до кожного елемента union-типу (string, number, boolean),
а потім об'єднує результати - це називається дистрибутивна поведінка умовних типів.
Приклад 6: Зворотний варіант - вилучення лише рядків
type ExtractString<T> = T extends string ? T : never;
type A = ExtractString<string | number | boolean>;
// stringПриклад 7: Застосування з keyof
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:
const result = condition ? value1 : value2;Тип TS:
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 обгортають у кортеж [...]:
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 null | undefined ? 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 |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.