Система типов TypeScript является тьюринг-полным функциональным языком программирования, выполняющимся исключительно во время компиляции. В сеньор-разработке библиотек, дизайн-систем и enterprise-монорепозиториев ручное дублирование типов категорически неприемлемо. Инженерный стандарт требует использования декларативного вывода на уровне типов: Conditional Types, оператора сопоставления с образцом infer, Template Literal Types и отображаемых типов с переназначением ключей (Key Remapping).
User разработчики вручную объявляют CreateUserDto, UpdateUserDto, UserResponseDto и UserEventMap, любое изменение одного поля требует правок в 5 местах.bus.emit(eventName: string, payload: any) разрушает типобезопасность. Опечатка в имени события или неверный пейлоад приводят к скрытым сбоям в продакшене.Readonly<T> замораживает только первый уровень объекта. Вложенные свойства остаются полностью мутабельными.Входной тип: TEventMap = { userCreated: { id: string }; orderPaid: { amount: number } }
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Mapped Type with Key Remapping via as & Template Literals │
│ [K in keyof TEventMap as `on${Capitalize<string & K>}`]: (data: TEventMap[K]) => void │
└─────────────────────────────────────┬───────────────────────────────────────┘
│
▼
Результирующий тип EventHandlers:
{
onUserCreated: (data: { id: string }) => void;
onOrderPaid: (data: { amount: number }) => void;
}
(100% автодополнение и строгая проверка компилятора!)Использование T extends Promise<infer R> ? R : T для рекурсивного извлечения внутреннего типа промисов.
Рекурсивный обход всех вложенных объектов, массивов и коллекций с заморозкой мутирующих методов.
Конструирование методов-геттеров вида get${Capitalize<Prop>} с фильтрацией методов через Never.
Generic-класс EventBus<TMap> с проверкой совпадения полезной нагрузки и имени события на этапе набора кода.
Понимание механики дистрибутивности над naked type parameters и создание продвинутых фильтров.
У вас есть тип: type ToArray<T> = T extends any ? T[] : never;. Что вернет выражение ToArray<string | number>?
Компилятор сопоставляет структуру входного типа с целевым паттерном (например, функцией, промисом или массивом).
Если сопоставление успешно, компилятор привязывает переменную R к найденному фрагменту.
Тип R захватывается и становится доступен в истинной ветви условного оператора.
Позволяет извлекать тип аргументов функции, тип возвращаемого значения или тип элементов кортежа.
Тип вызывает сам себя для обработки бесконечной вложенности (например, DeepReadonly или Flatten).
TypeScript поддерживает рекурсию типов глубиной до 1 000 уровней.
Отображаемый тип переименовывает строковые ключи на лету или отфильтровывает ненужные свойства возвратом never.
Автоматическая генерация строгих контрактов без сторонних генераторов кода.
/**
* Рекурсивно превращает все вложенные свойства объекта и массивов в readonly.
* Замораживает мутирующие методы массивов.
*/
export type DeepReadonly<T> = T extends Function | boolean | number | string | null | undefined
? T
: T extends Array<infer U>
? ReadonlyArray<DeepReadonly<U>>
: T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
: T extends Set<infer M>
? ReadonlySet<DeepReadonly<M>>
: { readonly [K in keyof T]: DeepReadonly<T[K]> };
/**
* Рекурсивное распаковывание любых вложенных Promise (Promise<Promise<T>> -> T)
*/
export type DeepAwaited<T> = T extends Promise<infer R> ? DeepAwaited<R> : T;
/**
* Извлекает только строковые ключи объекта, значения которых являются функциями
*/
export type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
/**
* Автоматически генерирует интерфейс геттеров для всех полей объекта.
* Например: { name: string } -> { getName: () => string }
*/
export type AutoGetters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};export interface AppEventMap {
'user:registered': { userId: string; email: string; timestamp: number };
'order:created': { orderId: string; amount: number; currency: 'USD' | 'EUR' };
'order:paid': { orderId: string; transactionHash: string };
}
type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
export class TypeSafeEventBus<TEvents extends Record<string, any>> {
private listeners: Map<keyof TEvents, Set<EventHandler<any>>> = new Map();
/**
* Подписка на событие с автодополнением ключей и 100% проверкой типа payload
*/
public on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(handler);
// Возврат функции отписки
return () => {
this.listeners.get(event)?.delete(handler);
};
}
/**
* Эмиссия события с обязательной передачей корректных аргументов
*/
public async emit<K extends keyof TEvents>(event: K, payload: TEvents[K]): Promise<void> {
const handlers = this.listeners.get(event);
if (!handlers || handlers.size === 0) return;
const promises = Array.from(handlers).map((handler) => handler(payload));
await Promise.all(promises);
}
}
// Пример использования с автодополнением IDE
export const globalEventBus = new TypeSafeEventBus<AppEventMap>();При написании циклических рекурсивных типов (например, DeepReadonly над объектами с перекрестными ссылками User -> Order -> User) компилятор TypeScript аварийно останавливает вычисление с ошибкой Type instantiation is excessively deep and possibly infinite. Всегда добавляйте базовые условия выхода (T extends Function | primitive ? T : ...).
Если написать type NonNullable<T> = [T] extends [null | undefined] ? never : T, компилятор НЕ будет распределять объединение. Для дистрибутивности параметр T обязан быть открытым (naked): T extends null | undefined ? never : T.
При извлечении типов аргументов используйте infer R, а не any. Ключевое слово infer заставляет TypeScript строго захватить тип, тогда как any отключает проверку для всех зависимых выражений.
У вас есть тип type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;. Что вернет ReturnTypeOf<() => Promise<string>>?