TL;DR:
TypeScript 5.x предлагает мощные инструменты для создания устойчивых и масштабируемых приложений. В этой статье мы рассмотрим продвинутые паттерны, такие как Generics с ограничениями, Mapped Types, Template Literal Types и Conditional Types с использованием infer, которые помогут вам улучшить код и снизить количество runtime-ошибок.
Введение
TypeScript давно перестал быть просто “JavaScript с типами”. Сегодня это мощная система типов, которая позволяет выявлять сложные архитектурные ошибки еще на этапе разработки. Для full-stack разработчиков, работающих с React и Node.js, освоение продвинутых возможностей TypeScript — это не только способ избежать any, но и возможность создавать самодокументируемые и масштабируемые кодовые базы. В этой статье мы рассмотрим паттерны, которые я ежедневно использую в production-приложениях.
Generics с ограничениями
Generics позволяют создавать переиспользуемые компоненты, которые работают с различными типами, сохраняя при этом безопасность типов. Однако настоящая магия начинается, когда вы добавляете ограничения.
interface HasId {
id: string | number;
}
function getRecordById<T extends HasId>(records: T[], id: string | number): T | undefined {
return records.find(record => record.id === id);
}
Расширяя HasId, мы гарантируем, что любой тип, переданный в getRecordById, должен иметь свойство id. Это позволяет писать более безопасную логику для работы с данными.
Mapped Types и Key Remapping
Mapped Types позволяют создавать новые типы на основе существующих. Начиная с TypeScript 4.1+, появилась возможность Key Remapping, которая особенно полезна для создания API-оберток или менеджеров состояния.
type FormState = {
userName: string;
email: string;
};
type FormValidation = {
[K in keyof FormState as `validate${Capitalize<K>}`]: (value: FormState[K]) => boolean;
};
// Результат:
// {
// validateUserName: (value: string) => boolean;
// validateEmail: (value: string) => boolean;
// }
Template Literal Types
Template Literal Types позволяют использовать интерполяцию строк в системе типов. Они идеально подходят для соблюдения соглашений по именованию или паттернов маршрутизации.
type Color = 'primary' | 'secondary';
type Intensity = 'low' | 'high';
type ThemeClass = `bg-${Color}-${Intensity}`;
const myButton: ThemeClass = 'bg-primary-high'; // Valid
const brokenButton: ThemeClass = 'bg-red-low'; // Error!
Conditional Types и infer
Conditional Types работают как тернарные операторы для типов. В сочетании с ключевым словом infer они позволяют “извлекать” типы из сложных структур, таких как возвращаемый тип функции или Promise.
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function fetchUser() {
return { id: 1, name: "Arslan" };
}
type User = GetReturnType<typeof fetchUser>; // { id: number, name: string }
Практическое применение
Использование продвинутых паттернов TypeScript не делает ваш код сложнее — оно переносит нагрузку проверок с runtime на этап компиляции. Это позволяет сократить количество unit-тестов, улучшить опыт разработчиков и создавать более предсказуемые системы.
Заключение
TypeScript 5.x предлагает мощные инструменты для улучшения качества и масштабируемости вашего кода. Используя Generics с ограничениями, Mapped Types, Template Literal Types и Conditional Types с infer, вы можете создавать более устойчивые и предсказуемые приложения. Какой из этих паттернов ваш любимый? Делитесь в комментариях!