О нас Руководства Проекты Контакты
Админка
пожалуйста подождите

Система типов TypeScript исключительно мощная и позволяет применять паттерны, которые выявляют ошибки на этапе компиляции, а не во время выполнения. Переход от базовых типов к продвинутым паттернам существенно повышает качество кода и удобство разработки. В этом руководстве рассматриваются паттерны TypeScript, которыми должен владеть каждый senior-разработчик.

Зачем нужен продвинутый TypeScript

Освоение продвинутого TypeScript позволяет:

  1. Безопасность на этапе компиляции: выявлять ошибки до попадания в production
  2. Лучший IntelliSense: IDE понимает структуру вашего кода
  3. Самодокументируемый код: типы описывают намерение
  4. Уверенность при рефакторинге: проверка типов выявляет ломающие изменения
  5. Проектирование API: выражать ограничения в системе типов

Utility Types

Встроенные Utility Types

interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Partial: все свойства опциональны
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; ... }
// Required: все свойства обязательны
type CompleteUser = Required<Partial<User>>;
// Pick: выбрать конкретные свойства
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// Omit: исключить конкретные свойства
type UserWithoutPassword = Omit<User, 'password'>;
// { id: number; name: string; email: string; createdAt: Date; }
// Readonly: все свойства только для чтения
type ImmutableUser = Readonly<User>;
// Нельзя переназначать свойства
// Record: создать тип объекта с ключами и типом значения
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
// { [key: string]: 'admin' | 'user' | 'guest' }
// Extract: извлечь типы из union
type StringOrNumber = string | number | boolean;
type OnlyStrings = Extract<StringOrNumber, string>;
// string
// Exclude: удалить типы из union
type NotString = Exclude<StringOrNumber, string>;
// number | boolean
// NonNullable: удалить null и undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// string
// ReturnType: получить тип возвращаемого значения функции
function createUser() {
return { id: 1, name: 'John' };
}
type NewUser = ReturnType<typeof createUser>;
// { id: number; name: string; }
// Parameters: получить типы параметров функции
function greet(name: string, age: number) {}
type GreetParams = Parameters<typeof greet>;
// [string, number]

Generics

Базовые Generics

// Generic-функция
function identity<T>(value: T): T {
return value;
}
const num = identity(42); // number
const str = identity('hello'); // string
// Generic-интерфейс
interface Container<T> {
value: T;
getValue(): T;
}
// Generic-класс
class Box<T> {
constructor(private content: T) {}
getContent(): T {
return this.content;
}
}
const numberBox = new Box(123);
const stringBox = new Box('hello');

Ограниченные Generics

// Ограничение с extends
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}
logLength('hello'); // OK
logLength([1, 2, 3]); // OK
logLength({ length: 5 }); // OK
// logLength(123); // Error: у number нет length
// Несколько ограничений
interface HasId {
id: number;
}
interface HasName {
name: string;
}
function merge<T extends HasId, U extends HasName>(a: T, b: U): T & U {
return { ...a, ...b };
}
// Ограничение через keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // number
// getProperty(user, 'invalid'); // Error

Generic-типы по умолчанию

interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
// Используется значение по умолчанию
const response: ApiResponse = { data: {}, status: 200, message: 'OK' };
// Явно заданный тип
const userResponse: ApiResponse<User> = {
data: { id: 1, name: 'John', email: '[email protected]', password: '', createdAt: new Date() },
status: 200,
message: 'OK'
};

Conditional Types

Базовые Conditional Types

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Практический пример: распаковать Promise
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type PromiseString = Promise<string>;
type Unwrapped = Unwrap<PromiseString>; // string
type NotPromise = Unwrap<number>; // number
// Тип элемента массива
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type StringArrayElement = ArrayElement<string[]>; // string

Дистрибутивные Conditional Types

type ToArray<T> = T extends any ? T[] : never;
// Распределяется по union
type StringOrNumberArray = ToArray<string | number>;
// string[] | number[]
// Предотвратить распределение с помощью []
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Combined = ToArrayNonDist<string | number>;
// (string | number)[]

Mapped Types

Базовые Mapped Types

// Сделать все свойства опциональными
type Optional<T> = {
[K in keyof T]?: T[K];
};
// Сделать все свойства обязательными
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Сделать все свойства только для чтения
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Убрать readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};

Продвинутые Mapped Types

// Преобразовать типы свойств
type Stringify<T> = {
[K in keyof T]: string;
};
// Отфильтровать свойства по типу значения
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
}
type StringProps = OnlyStrings<Mixed>;
// { name: string; email: string; }
// Переименовать свойства
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

Template Literal Types

Базовые Template Literals

type Greeting = `Hello, ${string}!`;
const valid: Greeting = 'Hello, World!'; // OK
// const invalid: Greeting = 'Hi, World!'; // Error
// Комбинирование union
type Color = 'red' | 'green' | 'blue';
type Size = 'small' | 'medium' | 'large';
type ColoredSize = `${Color}-${Size}`;
// 'red-small' | 'red-medium' | 'red-large' | 'green-small' | ...
// HTTP-методы
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts';
type Route = `${Method} ${Endpoint}`;

Типы обработчиков событий

type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type ChangeEvent = EventName<'change'>; // 'onChange'
// Создать тип обработчика событий
type EventHandlers<T> = {
[K in keyof T as EventName<string & K>]: (value: T[K]) => void;
};
interface FormFields {
name: string;
email: string;
age: number;
}
type FormHandlers = EventHandlers<FormFields>;
// { onName: (value: string) => void; onEmail: (value: string) => void; onAge: (value: number) => void; }

Discriminated Unions

Сопоставление с образцом

interface Loading {
status: 'loading';
}
interface Success<T> {
status: 'success';
data: T;
}
interface Error {
status: 'error';
error: string;
}
type AsyncState<T> = Loading | Success<T> | Error;
function handleState<T>(state: AsyncState<T>): string {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${JSON.stringify(state.data)}`;
case 'error':
return `Error: ${state.error}`;
}
}
// Проверка исчерпываемости
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
function handleStateExhaustive<T>(state: AsyncState<T>): string {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${JSON.stringify(state.data)}`;
case 'error':
return `Error: ${state.error}`;
default:
return assertNever(state); // Ошибка компиляции, если отсутствует case
}
}

Type Guards

Пользовательские Type Guards

interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
// Предикат типа
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow(); // TypeScript знает, что это Cat
} else {
animal.bark(); // TypeScript знает, что это Dog
}
}
// Функция-утверждение (выбрасывает исключение, если false)
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value is not a string');
}
}
function processValue(value: unknown) {
assertIsString(value);
// TypeScript знает, что здесь value — string
console.log(value.toUpperCase());
}

Declaration Merging

Расширение существующих типов

// Расширить Window
declare global {
interface Window {
analytics: {
track: (event: string, data?: object) => void;
};
}
}
// Теперь валидно
window.analytics.track('pageview');
// Расширить Express Request
declare namespace Express {
interface Request {
user?: {
id: string;
role: string;
};
}
}
// Расширить module
declare module 'express' {
interface Request {
customProperty: string;
}
}

Практические паттерны

Паттерн Builder с типами

class RequestBuilder<T extends object = {}> {
private config: T = {} as T;
withUrl<U extends string>(url: U): RequestBuilder<T & { url: U }> {
return Object.assign(this, { config: { ...this.config, url } });
}
withMethod<M extends 'GET' | 'POST'>(method: M): RequestBuilder<T & { method: M }> {
return Object.assign(this, { config: { ...this.config, method } });
}
build(): T {
return this.config;
}
}
const request = new RequestBuilder()
.withUrl('/api/users')
.withMethod('GET')
.build();
// Тип: { url: '/api/users'; method: 'GET'; }

Ключевые выводы

  1. Используйте utility types: не изобретайте заново Partial, Pick, Omit
  2. Ограничивайте generics: extends делает типы более полезными
  3. Discriminated unions: мощное сопоставление с образцом
  4. Type guards: безопасно сужайте типы
  5. Template literals: типобезопасные манипуляции со строками
  6. Mapped types: системно преобразуйте форму типов

Система типов TypeScript — это язык внутри языка: вложитесь в её глубокое изучение, и качество вашего кода заметно улучшится.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
Зетка Интерактив