Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Conditional generics». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Конструкція `T extends U ? X : Y` називається **умовним типом (conditional type)** і працює подібно до оператора `?:` у JavaScript, але на рівні типів: якщо тип `T` сумісний з типом `U`, результатом буде тип `X`, інакше - тип `Y`. **Ключове:** коли `T` - union-тип, умовний тип застосовується до кожного члена об'єднання окремо - це називається дистрибутивна поведінка.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)ЗображенняКонструкція вигляду `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 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` |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.