Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Generic без extends». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Generic без `extends` (наприклад `function identity<T>(value: T): T`) означає, що `T` може бути абсолютно будь-яким типом, тому компілятор не знає, які властивості чи методи у нього є. **Ключове:** якщо generic не обмежений, розробник не може безпечно звертатися до властивостей типу `T`, що часто призводить до спокуси використати `as any` і робить код небезпечним.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 1. Що означає "generic без `extends`" Коли ти оголошуєш generic ось так: ```javascript function identity<T>(value: T): T { return value; } ``` - ти кажеш TypeScript: > "`T` - може бути **абсолютно будь-яким типом**." І це справді так: `T` може бути числом, рядком, об'єктом, масивом, функцією, `null`, `undefined`, `void`, `never`, чим завгодно. --- ## 2. У чому проблема? Проблема не в самому generic, а в **припущеннях**, які розробник робить про тип `T`. Подивимось на приклад: ```javascript function getLength<T>(value: T): number { return value.length; // Помилка: у T може не бути length! } ``` TypeScript лається, бо `T` може бути чим завгодно - навіть `number` або `boolean`, і тоді `length` не існує. --- ## Чому це небезпечно Якби TypeScript **не перевіряв** це, ти міг би написати такий код: ```javascript function unsafeLength<T>(value: T): number { return (value as any).length; } console.log(unsafeLength(123)); // undefined console.log(unsafeLength(true)); // undefined ``` Код компілюється, але поведінка **непередбачувана** - runtime-помилки гарантовані. TypeScript більше тебе не захищає. --- ## 3. Як це виправити - додати `extends` Ти можеш **обмежити generic**, щоб він підходив лише під певні типи: ```javascript function getLength<T extends { length: number }>(value: T): number { return value.length; // безпечно } ``` Тепер `T` повинен бути об'єктом (або масивом, рядком тощо), у якого **гарантовано є властивість** `length`. Працює: ```javascript getLength("hello"); // 5 getLength([1, 2, 3]); // 3 ``` Не працює: ```javascript getLength(42); // Помилка - number не має length ``` > Завдяки `extends` TypeScript **знає**, які властивості можна використовувати. --- ## 4. Приклад небезпечного generic без `extends` ```javascript function merge<T, U>(a: T, b: U): T & U { return { ...a, ...b }; } merge(1, "test"); // Логічно безглуздо, але TS не забороняє ``` > TypeScript не знає, що `T` і `U` мають бути об'єктами - > і дозволяє передати числа, рядки і навіть `null`. --- Безпечний варіант: ```javascript function merge<T extends object, U extends object>(a: T, b: U): T & U { return { ...a, ...b }; } merge({ name: "Tim" }, { age: 25 }); // OK merge(1, "test"); // Помилка - не об'єкт ``` Тепер TypeScript строго стежить за структурою даних. --- ## 5. Ще приклад - з ключами Без `extends` можна легко "зламати" типову логіку: ```javascript function getValue<T, K>(obj: T, key: K) { return obj[key]; // Помилка: у T може не бути властивості K } ``` Правильно - обмежити `K`: ```javascript function getValue<T, K extends keyof T>(obj: T, key: K) { return obj[key]; // безпечно } getValue({ name: "Tim", age: 25 }, "name"); // OK getValue({ name: "Tim" }, "invalid"); // Помилка - немає такого ключа ``` > Завдяки `extends keyof T` TypeScript знає, > що `key` повинен бути **наявною властивістю** об'єкта. --- ## 6. Чому це важливо Якщо generic не обмежений (`T` без `extends`): - ти не можеш безпечно використовувати властивості чи методи цього типу; - TypeScript не знає, які операції допустимі; - з'являються спокуси "затикати" типи через `as any`, що робить код небезпечним. --- ## 7. Правило | Ситуація | Що робити | | --- | --- | | Використовуєш generic, але не звертаєшся до властивостей | `function identity<T>(x: T): T` - нормально | | Використовуєш властивості типу (`.length`, `.id` тощо) | Додай `extends { length: number }` | | Використовуєш ключі об'єкта | Додай `K extends keyof T` | | Очікуєш, що generic - об'єкт | Додай `extends object` | | Хочеш обмежити до нащадків інтерфейсу | Додай `extends MyInterface` | --- ## Підсумок | Без `extends` | З `extends` | | --- | --- | | `T` може бути *будь-яким* типом | `T` обмежений певною структурою | | Компілятор не знає, які властивості є | Компілятор перевіряє допустимі властивості | | Можуть бути runtime-помилки | Помилки ловляться на етапі компіляції | | Часто потребує `as any` | Майже не потребує `as` | | Менш передбачуваний код | Безпечний і самодокументований код | --- **Простіше кажучи:** > Generic без `extends` - це "універсальний контейнер без обмежень". > Без обмежень TypeScript не може тебе захистити. > > Додавай `extends`, коли generic повинен задовольняти **певному контракту** (структурі, інтерфейсу чи ключам).Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.