Пояснення методів метапрограмування TypeScript

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

1. Використання Generics для гнучкого коду

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

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

const num = identity<number>(42);
const str = identity<string>("Hello");

У цьому прикладі <T> дозволяє функції identity приймати будь-який тип і повертати той самий тип, забезпечуючи гнучкість і безпеку типу.

2. Вивід типу та умовні типи

Система визначення типу TypeScript автоматично визначає типи виразів. Крім того, умовні типи дають змогу створювати типи, які залежать від умов, що дозволяє застосовувати більш розширені методи метапрограмування.

type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>;  // true
type Test2 = IsString<number>;  // false

У цьому прикладі IsString є умовним типом, який перевіряє, чи даний тип T розширює string. Він повертає true для рядків і false для інших типів.

3. Відображені типи

Відображені типи — це спосіб трансформувати один тип в інший шляхом повторення властивостей типу. Це особливо корисно в метапрограмуванні для створення варіацій існуючих типів.

type ReadOnly<T> = {
  readonly [K in keyof T]: T[K];
};

interface User {
  name: string;
  age: number;
}

const user: ReadOnly<User> = {
  name: "John",
  age: 30,
};

// user.name = "Doe";  // Error: Cannot assign to 'name' because it is a read-only property.

Тут ReadOnly — це зіставлений тип, який робить усі властивості даного типу readonly. Це гарантує, що властивості об’єктів цього типу не можуть бути змінені.

4. Типи літеральних шаблонів

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

type WelcomeMessage<T extends string> = `Welcome, ${T}!`;

type Message = WelcomeMessage<"Alice">;  // "Welcome, Alice!"

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

5. Визначення рекурсивних типів

TypeScript допускає рекурсивні типи, тобто типи, які посилаються на самі себе. Це особливо корисно для метапрограмування, коли ви маєте справу зі складними структурами даних, такими як об’єкти JSON або глибоко вкладені дані.

type Json = string | number | boolean | null | { [key: string]: Json } | Json[];

const data: Json = {
  name: "John",
  age: 30,
  friends: ["Alice", "Bob"],
};

У цьому прикладі Json — це рекурсивний тип, який може представляти будь-яку дійсну структуру даних JSON, що забезпечує гнучке представлення даних.

6. Декоратори для метапрограмування

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

function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);  // Logs: "Calling add with [2, 3]"

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

Висновок

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