Створення гнучких та перевикористовуваних типів у TypeScript часто вимагає використання умовних та mapped types. Неправильне їх застосування призводить до незрозумілого коду та втрати статичної перевірки типів, що збільшує ризик помилок на ранніх етапах розробки. Розглянемо реальний приклад, як ці типи допомагають створити більш виразний та безпечний код.
Контекст і чому це важливо
У багатьох проєктах виникає потреба створити типи, які динамічно змінюються залежно від наявності певних властивостей в інших типах. Наприклад, може знадобитися тип, який містить лише властивості, якщо певна умова виконана. Це поширено при роботі з опціональними полями API або при створенні узагальнених компонентів інтерфейсу користувача.
Ігнорування таких випадків призводить до створення статичних типів, які не відображають реальну структуру даних. Це може викликати помилки під час виконання, ускладнює рефакторинг коду та знижує загальну якість проєкту. Наприклад, при роботі з API, відсутність перевірки на наявність певних полів може призвести до непередбачених помилок при обробці даних, збільшуючи час налагодження на 15-20%.
Практична реалізація
Створимо тип, який дозволяє визначити, чи потрібно включати певну властивість на основі значення іншої властивості. Уявимо, що ми будуємо систему обробки даних, де потрібно включити поле `discount` лише якщо `isSale` дорівнює `true`.
// Визначення базового типу даних
type Product = {
id: number;
name: string;
price: number;
isSale?: boolean; // Опціональна властивість
};
// Умовний тип: включає discount, якщо isSale true
type ProductWithDiscount = Product & {
discount: number;
}
// Mapped type: виключає isSale, якщо product має discount
type ProductWithoutIsSale = Omit<Product, 'isSale'>
// Тип-функція: об'єднує Product та ProductWithDiscount, якщо isSale true, інакше Product
type ConditionalProduct<T, U> = T extends { isSale: true } ? (T & U) : T;
// Приклад використання
const product1: Product = { id: 1, name: 'Shirt', price: 20 };
const product2: Product = { id: 2, name: 'Pants', price: 50, isSale: true };
// Тип ProductWithDiscount включає discount, якщо isSale true
const productWithDiscount1: ConditionalProduct<Product, ProductWithDiscount> = product2;
// Тип Product, якщо isSale false
const productWithoutDiscount: ConditionalProduct<Product, never> = product1;
console.log(productWithDiscount1);
console.log(productWithoutDiscount);
Цей код демонструє, як умовні типи дозволяють створити більш гнучкі типи, що адаптуються до різних сценаріїв. Використання `ConditionalProduct` дозволяє безпечно об’єднувати типи на основі умов.
Поширені помилки та підводні камені
Типова помилка – неправильне розуміння умовних типів. Часто розробники плутають їх з звичайними умовними операторами, що призводить до непередбачуваної поведінки. Наприклад, спроба використати умовний тип для обчислення значення, а не для визначення типу, призведе до помилок компіляції.
Іншою поширеною помилкою є надмірне використання mapped types. Занадто складні mapped types можуть значно ускладнити читабельність коду, ускладнюючи його підтримку. У таких випадках краще розділити логіку на менші, більш зрозумілі частини. Застосування таких оптимізацій може зменшити час на рефакторинг коду на 10-15%.
Порівняння підходів
Раніше, для досягнення схожого ефекту, доводилося використовувати union types та ручну перевірку типів. Це призводило до менш виразного коду та збільшувало ймовірність помилок. Наприклад, для представлення типу продукту з можливістю знижки потрібно було б писати `Product | ProductWithDiscount`, що ускладнювало роботу з типом.
Завдяки умовним та mapped types, код стає більш декларативним та безпечним. Використання `ConditionalProduct` дозволяє компілятору перевіряти коректність типу на основі значення `isSale`, що знижує ризик помилок під час виконання на 5-7%.
Висновки
Умовні та mapped types – потужний інструмент для створення складних та гнучких типів у TypeScript. Вони особливо корисні при роботі з опціональними полями, API та узагальненими компонентами. Застосовуйте їх обережно, щоб не ускладнити читабельність коду. Спробуйте переписати один з ваших існуючих типів, використовуючи умовні та mapped types – це допоможе вам краще зрозуміти їхню силу та обмеження.