← все статьи
11 мин

TypeScript: паттерны, которые реально спасают

TypeScript – не просто "JavaScript с типами". Его система типов тьюринг-полная и позволяет кодировать сложные инварианты. Вот паттерны, которые мы используем постоянно – не ради красоты, а потому что они ловят ошибки до рантайма.

Discriminated Union: забудьте про boolean флаги

Классическая проблема: объект с кучей опциональных полей, которые зависят от состояния. Легко ошибиться и обратиться к полю, которого нет в данном состоянии.

// Плохо – компилятор не поможет type ApiResponse = { isLoading: boolean; data?: User; error?: string; } // Хорошо – каждое состояние явно type ApiResponse = | { status: 'loading' } | { status: 'success'; data: User } | { status: 'error'; error: string; code: number } function render(response: ApiResponse) { switch (response.status) { case 'loading': return ; case 'success': return ; case 'error': return ; // TypeScript знает: в каждой ветке доступны только нужные поля } }

Branded Types: не перепутайте ID

userId и orderId – оба string. Компилятор не скажет, если вы передадите одно вместо другого. Branded types решают это без рантайм-оверхеда.

type UserId = string & { readonly _brand: 'UserId' }; type OrderId = string & { readonly _brand: 'OrderId' }; // Конструкторы – единственный способ создать значение const UserId = (id: string): UserId => id as UserId; const OrderId = (id: string): OrderId => id as OrderId; function getOrder(id: OrderId): Promise { ... } const uid = UserId('user-123'); const oid = OrderId('order-456'); getOrder(oid); // ✓ getOrder(uid); // ✗ Argument of type 'UserId' is not assignable to 'OrderId'

satisfies: валидация без потери вывода типа

Оператор satisfies (TS 4.9) позволяет проверить, что объект соответствует типу, не теряя при этом вывод конкретных литеральных типов.

type Config = { theme: 'light' | 'dark'; lang: string; retries: number; } // С обычной аннотацией: config.theme имеет тип 'light' | 'dark' const config: Config = { theme: 'dark', lang: 'ru', retries: 3 }; // С satisfies: config.theme имеет тип 'dark' – более точный вывод const config = { theme: 'dark', lang: 'ru', retries: 3 } satisfies Config; // Ошибка поймается: const bad = { theme: 'purple', // ✗ Type '"purple"' is not assignable lang: 'ru', retries: 3 } satisfies Config;

infer в Conditional Types: извлекаем типы

infer позволяет "вытащить" часть типа внутри conditional type. Полезно для работы с промисами, массивами, функциями.

// Достать тип, который возвращает промис type Awaited = T extends Promise ? R : T; type A = Awaited>; // string type B = Awaited>; // User[] type C = Awaited; // number // Параметры функции type Params any> = T extends (...args: infer P) => any ? P : never; type LoginParams = Params; // [email: string, password: string]

Template Literal Types: типы как строки

Генерация типов из строковых паттернов – мощный инструмент для event-driven систем и API-маршрутов.

type EventName = 'created' | 'updated' | 'deleted';
type Entity    = 'user' | 'order' | 'product';

type AppEvent = `${Entity}:${EventName}`;
// 'user:created' | 'user:updated' | 'user:deleted' |
// 'order:created' | ...

function on(event: AppEvent, handler: () => void): void { ... }

on('user:created', handler);  // ✓
on('user:banned',  handler);  // ✗ – такого события нет

← все статьи Следующая →Redis: больше чем кэш