Паттерны в TypeScript
Play ITЗагрузка интерактивного демо…
Дальше: Дженерики · Обработка ошибок · Объекты и классы · Справочник — распространённые паттерны
Паттерны проектирования в TypeScript усиливаются типами: компилятор проверяет исчерпывающие switch, контракты фабрик и форму DTO. Ниже — приёмы, которые чаще всего встречаются в frontend и Node, без формального каталога GoF. Теория паттернов — парадигмы и уровни абстракции. Удобный порядок — типы и типизация, классы, эта статья, обработка ошибок. Таблицы — Справочник — распространённые паттерны.
Discriminated union (команды и состояния)
Код ITЗагрузка примера кода…
Разбор:
- Поле
type— дискриминант; в ветке доступны только нужные поля. neverвdefault— страховка при расширении union — Операторы и условные ветвления в TypeScript.
Применение — Redux actions, очереди сообщений, UI state — Асинхронное программирование в TypeScript, TypeScript и React.
Branded types (номинальность поверх structural)
type UserId = string & { readonly __brand: "UserId" };
type OrderId = string & { readonly __brand: "OrderId" };
function userId(raw: string): UserId {
return raw as UserId;
}
function loadOrder(id: OrderId): void {
console.log(id);
}
const u = userId("u-1");
loadOrder(u); // ошибка: OrderId ≠ UserId
Разбор:
- Оба в runtime —
string, но TS запрещает перепутать. - Валидацию формата (UUID) делайте в функции-конструкторе
userId.
Factory по интерфейсу
Код ITЗагрузка примера кода…
Разбор:
- Код зависит от
Logger, без привязки к конкретному классу — проще тесты (mock). - Structural typing: любой объект с
logподойдёт, если не нужна номинальность.
DTO + mapper между слоями
Код ITЗагрузка примера кода…
Разбор:
- API (snake_case, string date) ≠ domain (
Date). - Один mapper на границе — не размазывать преобразования — TypeScript и Node.js.
Type-safe event bus
Код ITЗагрузка примера кода…
Разбор:
K extends keyof Eventsсвязывает имя события и форму payload — тот же приём, что у updater: первый аргумент сужает второй.- Literal union в
currencyиroleловит опечатки и неверные коды до запуска. - В колбэке
onдоступны только поля выбранного события; деструктуризация чужого поля — ошибка компиляции. - В продакшене — typed wrappers над EventEmitter или фреймворк.
- Тот же механизм
PayloadMap[E]для отдельных handler-функций и константas const— дженерики §сопоставленные типы.
Builder с цепочкой
Код ITЗагрузка примера кода…
Разбор:
thisreturn type сохраняет fluent API в подклассах.- Для SQL в продакшене — query builder библиотеки, без ручных строк.
Repository (интерфейс + реализация)
Код ITЗагрузка примера кода…
Связь с Объекты и классы в TypeScript, TypeORM.
Примеси (mixins)
В TypeScript нет ключевого слова mixin, но примесь — это функция, которая принимает класс-конструктор и возвращает расширенный класс. Так комбинируют поведение без глубокого наследования.
Код ITЗагрузка примера кода…
Разбор:
TimestampedиIdentifiable— примеси; порядок обёртки влияет на цепочкуsuper.- Типы параметров конструктора в реальных проектах сужают через
Constructorи дженерики — Дженерики в TypeScript. - В React чаще используют композицию хуков; классовые mixins — реже. В Nest/ORM — декораторы и базовые классы — Декораторы в TypeScript.
Альтернатива примесям для объектов без классов: пересечение типов + spread ({ ...a, ...b }) или отдельные функции-хелперы — 18.md §композиция.
Anti-patterns
| Плохо | Лучше |
|---|---|
any на границе модуля | unknown + mapper |
| Строковые коды ошибок без union | type ErrorCode = "..." | "..." |
| God-object type на 50 полей | разбить DTO / domain |
as везде | guards, Zod |
| Дублировать union вручную | as const + typeof — Типы данных и типизация в TypeScript |
Частые ошибки
| Ошибка | Что делать |
|---|---|
| Union без дискриминанта | добавить поле type / kind |
Забыли never в switch | exhaustive check |
| Branded type без factory | один вход userId() |
| Mapper в UI-компоненте | слой api/ или mappers/ |
Практика
- Опишите 3 команды
CommandиhandleCommandсnever. - Введите
UserId/OrderIdbranded и поймайте ошибку присваивания. - Сделайте
fromDto/toDtoдля одной сущности. - Добавьте
TypedEmitterс двумя событиями. - Вынесите
Repositoryinterface и in-memory реализацию.