Українська

Дослідіть розширені дженерики TypeScript: обмеження, утилітарні типи, виведення та практичне застосування для написання надійного коду в глобальному контексті.

Дженерики в TypeScript: розширені патерни використання

Дженерики в TypeScript — це потужна функція, яка дозволяє писати більш гнучкий, повторно використовуваний та типобезпечний код. Вони дають змогу визначати типи, що можуть працювати з різними іншими типами, зберігаючи при цьому перевірку типів на етапі компіляції. Ця стаття заглиблюється в розширені патерни використання, надаючи практичні приклади та ідеї для розробників усіх рівнів, незалежно від їхнього географічного розташування чи досвіду.

Розуміння основ: короткий огляд

Перш ніж заглиблюватися в складніші теми, давайте швидко повторимо основи. Дженерики дозволяють створювати компоненти, які можуть працювати з різними типами, а не з одним єдиним. Ви оголошуєте параметр узагальненого типу в кутових дужках (`<>`) після назви функції або класу. Цей параметр діє як заповнювач для фактичного типу, який буде вказано пізніше, коли функція або клас використовуватиметься.

Наприклад, проста узагальнена функція може виглядати так:

function identity(arg: T): T {
  return arg;
}

У цьому прикладі T — це параметр узагальненого типу. Функція identity приймає аргумент типу T і повертає значення типу T. Потім ви можете викликати цю функцію з різними типами:


let stringResult: string = identity("hello");
let numberResult: number = identity(42);

Розширені дженерики: за межами основ

Тепер давайте розглянемо більш витончені способи використання дженериків.

1. Обмеження узагальнених типів

Обмеження типів дозволяють вам звузити коло типів, які можна використовувати з параметром узагальненого типу. Це надзвичайно важливо, коли вам потрібно переконатися, що узагальнений тип має певні властивості або методи. Ви можете використовувати ключове слово extends для визначення обмеження.

Розглянемо приклад, де функція повинна мати доступ до властивості length:

function loggingIdentity(arg: T): T {
  console.log(arg.length);
  return arg;
}

У цьому прикладі T обмежений типами, що мають властивість length типу number. Це дозволяє нам безпечно звертатися до arg.length. Спроба передати тип, який не задовольняє це обмеження, призведе до помилки на етапі компіляції.

Глобальне застосування: Це особливо корисно в сценаріях обробки даних, таких як робота з масивами або рядками, де часто потрібно знати їхню довжину. Цей патерн працює однаково, незалежно від того, перебуваєте ви в Токіо, Лондоні чи Ріо-де-Жанейро.

2. Використання дженериків з інтерфейсами

Дженерики бездоганно працюють з інтерфейсами, дозволяючи вам визначати гнучкі та повторно використовувані визначення інтерфейсів.

interface GenericIdentityFn {
  (arg: T): T;
}

function identity(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn = identity;

Тут GenericIdentityFn — це інтерфейс, який описує функцію, що приймає узагальнений тип T і повертає той самий тип T. Це дозволяє визначати функції з різними сигнатурами типів, зберігаючи при цьому типобезпечність.

Глобальна перспектива: Цей патерн дозволяє створювати повторно використовувані інтерфейси для різних видів об'єктів. Наприклад, ви можете створити узагальнений інтерфейс для об'єктів передачі даних (DTO), що використовуються в різних API, забезпечуючи узгоджені структури даних у вашому додатку незалежно від регіону, де він розгорнутий.

3. Узагальнені класи

Класи також можуть бути узагальненими:


class GenericNumber {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

Цей клас GenericNumber може містити значення типу T та визначати метод add, що працює з типом T. Ви створюєте екземпляр класу з потрібним типом. Це може бути дуже корисним для створення структур даних, таких як стеки або черги.

Глобальне застосування: Уявіть собі фінансовий додаток, якому потрібно зберігати та обробляти різні валюти (наприклад, USD, EUR, JPY). Ви можете використовувати узагальнений клас для створення класу `CurrencyAmount`, де `T` представляє тип валюти, що дозволяє проводити типобезпечні розрахунки та зберігати суми в різних валютах.

4. Кілька параметрів типу

Дженерики можуть використовувати кілька параметрів типу:


function swap(a: T, b: U): [U, T] {
  return [b, a];
}

let result = swap("hello", 42);
// result[0] is number, result[1] is string

Функція swap приймає два аргументи різних типів і повертає кортеж із поміняними місцями типами.

Глобальна актуальність: У міжнародних бізнес-додатках у вас може бути функція, яка приймає дві пов'язані частини даних з різними типами і повертає їх у вигляді кортежу, наприклад, ID клієнта (рядок) та вартість замовлення (число). Цей патерн не надає переваги жодній конкретній країні та ідеально адаптується до глобальних потреб.

5. Використання параметрів типу в узагальнених обмеженнях

Ви можете використовувати параметр типу всередині обмеження.


function getProperty(obj: T, key: K) {
  return obj[key];
}

let obj = { a: 1, b: 2, c: 3 };

let value = getProperty(obj, "a"); // value is number

У цьому прикладі K extends keyof T означає, що K може бути лише ключем типу T. Це забезпечує надійну типобезпечність при динамічному доступі до властивостей об'єкта.

Глобальна застосовність: Це особливо корисно при роботі з об'єктами конфігурації або структурами даних, де доступ до властивостей потрібно перевіряти під час розробки. Цю техніку можна застосовувати в додатках у будь-якій країні.

6. Узагальнені утилітарні типи

TypeScript надає кілька вбудованих утилітарних типів, які використовують дженерики для виконання поширених перетворень типів. До них належать:

Наприклад:


interface User {
  id: number;
  name: string;
  email: string;
}

// Partial - all properties optional
let optionalUser: Partial = {};

// Pick - only id and name properties
let userSummary: Pick = { id: 1, name: 'John' };

Глобальний приклад використання: Ці утиліти є безцінними при створенні моделей запитів та відповідей API. Наприклад, у глобальному додатку електронної комерції Partial можна використовувати для представлення запиту на оновлення (коли надсилаються лише деякі деталі продукту), тоді як Readonly може представляти продукт, що відображається у фронтенді.

7. Виведення типів з дженериками

TypeScript часто може виводити параметри типу на основі аргументів, які ви передаєте узагальненій функції або класу. Це може зробити ваш код чистішим і легшим для читання.


function createPair(a: T, b: T): [T, T] {
  return [a, b];
}

let pair = createPair("hello", "world"); // TypeScript infers T as string

У цьому випадку TypeScript автоматично виводить, що T є string, оскільки обидва аргументи є рядками.

Глобальний вплив: Виведення типів зменшує потребу в явних анотаціях типів, що може зробити ваш код більш стислим і читабельним. Це покращує співпрацю між різноманітними командами розробників, де можуть існувати різні рівні досвіду.

8. Умовні типи з дженериками

Умовні типи в поєднанні з дженериками надають потужний спосіб створювати типи, які залежать від значень інших типів.


type Check = T extends string ? string : number;

let result1: Check = "hello"; // string
let result2: Check = 42; // number

У цьому прикладі Check обчислюється як string, якщо T розширює string, інакше він обчислюється як number.

Глобальний контекст: Умовні типи надзвичайно корисні для динамічного формування типів на основі певних умов. Уявіть систему, яка обробляє дані залежно від регіону. Умовні типи можна використовувати для перетворення даних на основі специфічних для регіону форматів або типів даних. Це надзвичайно важливо для додатків з глобальними вимогами до управління даними.

9. Використання дженериків з відображеними типами (Mapped Types)

Відображені типи дозволяють трансформувати властивості одного типу на основі іншого. Поєднуйте їх з дженериками для гнучкості:


type OptionsFlags = {
  [K in keyof T]: boolean;
};

interface FeatureFlags {
  darkMode: boolean;
  notifications: boolean;
}

// Create a type where each feature flag is enabled (true) or disabled (false)
let featureFlags: OptionsFlags = {
  darkMode: true,
  notifications: false,
};

Тип OptionsFlags приймає узагальнений тип T і створює новий тип, де властивості T тепер відображаються на булеві значення. Це дуже потужно для роботи з конфігураціями або прапорами функцій.

Глобальне застосування: Цей патерн дозволяє створювати схеми конфігурації на основі налаштувань для конкретного регіону. Такий підхід дає змогу розробникам визначати конфігурації для конкретних регіонів (наприклад, мови, що підтримуються в регіоні). Це дозволяє легко створювати та підтримувати глобальні схеми конфігурації додатків.

10. Розширене виведення типів з ключовим словом infer

Ключове слово infer дозволяє витягувати типи з інших типів у межах умовних типів.


type ReturnType any> = T extends (...args: any) => infer R ? R : any;

function myFunction(): string {
  return "hello";
}

let result: ReturnType = "hello"; // result is string

Цей приклад виводить тип, що повертається функцією, за допомогою ключового слова infer. Це витончена техніка для більш просунутих маніпуляцій з типами.

Глобальна значущість: Ця техніка може бути життєво важливою у великих, розподілених глобальних програмних проєктах для забезпечення типобезпечності при роботі зі складними сигнатурами функцій та структурами даних. Вона дозволяє динамічно генерувати типи з інших типів, покращуючи підтримку коду.

Найкращі практики та поради

Висновок: використання потужності дженериків у глобальному масштабі

Дженерики TypeScript є наріжним каменем написання надійного та підтримуваного коду. Оволодівши цими розширеними патернами, ви можете значно покращити типобезпечність, повторне використання та загальну якість ваших JavaScript-додатків. Від простих обмежень типів до складних умовних типів, дженерики надають інструменти, необхідні для створення масштабованого та підтримуваного програмного забезпечення для глобальної аудиторії. Пам'ятайте, що принципи використання дженериків залишаються незмінними незалежно від вашого географічного розташування.

Застосовуючи методи, обговорені в цій статті, ви зможете створювати краще структурований, надійніший та легший для розширення код, що в кінцевому підсумку призведе до більш успішних програмних проєктів, незалежно від країни, континенту чи бізнесу, з яким ви пов'язані. Використовуйте дженерики, і ваш код вам подякує!