Пошук уроків, статей та іншого контенту
Як писати функції й типи, що працюють з довільним типом, не втрачаючи перевірки типів.
Функція, що просто повертає перший елемент масиву, логічно повинна працювати з масивом чисел, рядків, об'єктів — з будь-яким типом однаково. Написати її з типом any втрачає всі перевірки типів (виклик поверне any, і компілятор більше нічого не знає про реальний тип результату). Написати окрему версію функції під кожен конкретний тип — дублювання коду. Generics вирішують це третім способом: тип-параметр.
function firstElement<T>(arr: T[]): T {
return arr[0];
}
const num = firstElement([1, 2, 3]); // TypeScript вивів: num має тип number
const str = firstElement(["a", "b", "c"]); // TypeScript вивів: str має тип stringT — тип-параметр (ім'я умовне, T — конвенція для «type», так само як у деяких мовах); при кожному виклику компілятор підставляє замість T конкретний тип, виведений з переданого аргументу, і саме тому num має тип number, а не any.
interface ApiResponse<T> {
data: T;
status: number;
}
const userResponse: ApiResponse<User> = { data: { id: "1", status: "active" }, status: 200 };
const productsResponse: ApiResponse<Product[]> = { data: [], status: 200 };Той самий інтерфейс ApiResponse описує відповідь API з будь-яким типом даних усередині — без потреби писати окремий UserApiResponse, ProductsApiResponse тощо для кожного випадку.
Іноді функції потрібен не геть довільний тип, а тип із певною гарантованою формою — extends обмежує, які типи допустимі як T:
function getId<T extends { id: string }>(item: T): string {
return item.id; // безпечно — компілятор гарантує, що T завжди має поле id
}
getId({ id: "1", name: "Тарас" }); // ок
// getId({ name: "Тарас" }); // помилка компіляції — немає поля idinterface ApiResponse<T = unknown> {
data: T;
status: number;
}
const response: ApiResponse = { data: "будь-що", status: 200 }; // T виведено як unknown, якщо не вказано явноДженерики вже неявно траплялись у курсі React — компонент, типізований як useState<T>, використовує саме цей механізм: React.useState — дженерик-функція, і T виводиться з початкового значення, переданого при виклику.
Використовувати any там, де насправді потрібен дженерик, — any вимикає перевірку типів повністю, тоді як дженерик зберігає точний, конкретний тип для кожного виклику.
Забути extends там, де тип-параметру потрібна гарантована форма, — без обмеження компілятор дозволить будь-який тип T, включно з тими, що не мають очікуваних полів.
Надмірне ускладнення дженериками там, де конкретний, нединамічний тип був би простіший і зрозуміліший, — дженерики виправдані, коли функція/тип справді працює з різними типами узгоджено, а не «про всяк випадок».
Generics дозволяють писати функції й типи, параметризовані типом, що визначається при кожному конкретному використанні, — зберігаючи точну перевірку типів там, де any втратив би її повністю, а дублювання коду під кожен конкретний тип було б зайвим. extends обмежує допустимі типи певною гарантованою формою.