Часто в проєктах з’являється код, який повторює логіку обробки даних різних типів. Це призводить до збільшення обсягу коду, ускладнює підтримку та підвищує ймовірність помилок. Generics у TypeScript дозволяють писати більш гнучкий та багаторазовий код, уникнувши копіювання.
Розробники стикаються з цією проблемою, коли потрібно створити функції або класи, які працюють з різними типами даних, наприклад, обробка списку користувачів та список товарів. Без generics довелося б створювати окремі версії для кожного типу, що не масштабується.
Контекст і чому це важливо
Generics особливо корисні при роботі з колекціями даних, функціями перетворення та абстрактними структурами даних. Вони дозволяють писати код, який працює з будь-яким типом даних, не втрачаючи при цьому типову безпеку.
Ігнорування generics може призвести до написання великої кількості майже ідентичного коду, що збільшує розмір проєкту, ускладнює його розуміння та підвищує ймовірність помилок. Наприклад, замість однієї функції `processData
Практична реалізація
Створюємо узаганену функцію, яка приймає масив будь-якого типу даних та повертає новий масив, де кожен елемент збільшений на одиницю (для чисел) або переведений у верхній регістр (для рядків).
function processArray<T>(arr: T[]): T[] {
// Перевіряємо, чи елементи масиву числові
if (typeof arr[0] === 'number') {
// Якщо так, збільшуємо кожен елемент на 1
return arr.map(item => (item as number) + 1);
} else if (typeof arr[0] === 'string') {
// Якщо так, переводимо кожен елемент у верхній регістр
return arr.map(item => (item as string).toUpperCase());
} else {
// Якщо тип не підтримується, повертаємо оригінальний масив
return arr;
}
}
// Приклад використання з масивом чисел
const numbers = [1, 2, 3];
const processedNumbers = processArray(numbers); // [2, 3, 4]
// Приклад використання з масивом рядків
const strings = ["hello", "world"];
const processedStrings = processArray(strings); // ["HELLO", "WORLD"]
Цей код визначає функцію `processArray`, яка приймає масив `T` і повертає новий масив того ж типу. Використання generics дозволяє функції працювати з масивами різних типів, забезпечуючи типову безпеку. Перевірка типу елементів масиву дозволяє обробляти їх по-різному залежно від типу.
Поширені помилки та підводні камені
- Некоректне використання обмежень (constraints): Використання generics без обмежень може призвести до помилок під час компіляції, якщо код намагається використовувати властивості, які не гарантовано існують для всіх типів. Наприклад, `function process
(item: T) { return item.length; }` без обмежень `T extends { length: number }` призведе до помилки, якщо передати число. - Занадто широкі generics: Занадто загальні generics можуть ускладнити розуміння коду та зменшити його типову безпеку. Краще використовувати більш специфічні generics, коли це можливо. Наприклад, замість `Array
` краще використовувати `Array `. - Неявне перетворення типів: TypeScript може виконувати неявне перетворення типів, що може призвести до неочікуваної поведінки. Завжди перевіряйте типи даних, щоб уникнути помилок. Наприклад, `processArray([1, “2”])` може викликати проблеми.
Порівняння підходів
Без generics, при роботі з різними типами даних, ми б писали окремі функції для кожного типу. Це призводить до дублювання коду та ускладнює підтримку.
function processUsers(users: User[]) {
// Обробка користувачів
}
function processProducts(products: Product[]) {
// Обробка продуктів
}
З generics, ми можемо створити одну узаганену функцію, яка працює з будь-яким типом даних, скорочуючи обсяг коду приблизно на 50% і значно полегшуючи підтримку.
Висновки
Generics — потужний інструмент для написання гнучкого та багаторазового коду в TypeScript. Їх варто використовувати, коли потрібно створити компоненти, які працюють з різними типами даних. Почніть з простого прикладу, наприклад, узаганеного масиву, та експериментуйте з обмеженнями для покращення типової безпеки.