Антон Ларичев
Utility Types в TypeScript — полный разбор встроенных утилит
Введение
TypeScript поставляется с набором встроенных утилитарных типов (Utility Types), которые позволяют трансформировать существующие типы, не дублируя их. Знание этих инструментов — обязательный навык для работы с TypeScript: они используются повсюду в стандартной библиотеке, React, Angular и любом крупном проекте.
В этой статье разберём все ключевые Utility Types с примерами: когда применять, как они работают под капотом и как избежать типичных ошибок.
Partial<T>
Делает все свойства типа необязательными (добавляет ? к каждому):
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// }
Когда использовать: при обновлении объекта (PATCH-запросы), когда нужно передать только часть полей.
function updateUser(id: number, fields: Partial<User>): User {
// Обновляем только переданные поля
return { ...currentUser, ...fields };
}
updateUser(1, { name: 'Иван' }); // OK — только name
updateUser(1, { email: 'ivan@example.com', age: 30 }); // OK
Под капотом: type Partial<T> = { [P in keyof T]?: T[P] }.
Required<T>
Противоположность Partial — делает все свойства обязательными, убирает ?:
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
type RequiredConfig = Required<Config>;
// {
// host: string;
// port: number;
// debug: boolean;
// }
Когда использовать: когда нужно убедиться, что конфигурация заполнена полностью перед её применением.
Readonly<T>
Делает все свойства только для чтения — попытка изменить их вызывает ошибку компиляции:
interface Point {
x: number;
y: number;
}
const origin: Readonly<Point> = { x: 0, y: 0 };
origin.x = 5; // Ошибка: Cannot assign to 'x' because it is a read-only property.
Когда использовать: для иммутабельных данных, констант конфигурации, состояний Redux.
// Типичный паттерн в Redux
type State = Readonly<{
users: User[];
loading: boolean;
error: string | null;
}>;
Если вы хотите детально изучить систему типов TypeScript, включая Utility Types и дженерики — приходите на наш курс TypeScript с нуля. На курсе 180 уроков, AI-тренажёры для практики 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.
Pick<T, K>
Создаёт новый тип, включающий только указанные свойства:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Публичный профиль — без чувствительных данных
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// { id: number; name: string; email: string; }
Когда использовать: для DTO (Data Transfer Objects), представлений данных без чувствительных полей, пропсов компонентов.
// Компонент принимает только нужные поля User
function UserCard({ id, name }: Pick<User, 'id' | 'name'>) {
return <div>{id}: {name}</div>;
}
Omit<T, K>
Создаёт новый тип, исключая указанные свойства (противоположность Pick):
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Убираем password и createdAt
type PublicUser = Omit<User, 'password' | 'createdAt'>;
// { id: number; name: string; email: string; }
Когда использовать: когда нужно исключить несколько полей, а остальные сохранить. Особенно удобно, когда полей много и Pick пришлось бы писать длинно.
// Тип для создания пользователя — без id и createdAt (они генерируются на сервере)
type CreateUserDto = Omit<User, 'id' | 'createdAt'>;
Record<K, V>
Создаёт тип объекта с ключами типа K и значениями типа V:
// Словарь: ключ — строка, значение — число
type ScoreMap = Record<string, number>;
const scores: ScoreMap = {
Иван: 95,
Мария: 88,
Петр: 77
};
// Ключи — строгий union тип
type Status = 'active' | 'inactive' | 'pending';
type StatusLabels = Record<Status, string>;
const labels: StatusLabels = {
active: 'Активный',
inactive: 'Неактивный',
pending: 'Ожидает' // TypeScript потребует все три ключа
};
Когда использовать: для словарей с предсказуемой структурой, маппинга значений, кэшей.
Exclude<T, U>
Исключает из union-типа T все типы, которые входят в U:
type AllStatuses = 'active' | 'inactive' | 'pending' | 'deleted';
type ActiveStatuses = Exclude<AllStatuses, 'deleted' | 'inactive'>;
// 'active' | 'pending'
// Исключаем примитивы из union
type NonNullable<T> = Exclude<T, null | undefined>;
Extract<T, U>
Противоположность Exclude — оставляет только те типы из T, которые входят в U:
type A = 'a' | 'b' | 'c' | 'd';
type B = 'b' | 'd' | 'f';
type Common = Extract<A, B>; // 'b' | 'd'
// Фильтрация union по условию
type StringsOnly = Extract<string | number | boolean | null, string>;
// string
NonNullable<T>
Удаляет null и undefined из типа:
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
function processInput(value: string | null | undefined) {
const safe: NonNullable<typeof value> = value!; // убеждены, что не null
// ...
}
ReturnType<T>
Извлекает тип возвращаемого значения функции:
function getUser() {
return {
id: 1,
name: 'Иван',
email: 'ivan@example.com'
};
}
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string; }
Когда использовать: когда тип возвращаемого значения сложный или выводится автоматически, и вы хотите переиспользовать его без явного объявления.
// Полезно для функций-фабрик и хуков
function useAuth() {
return { user: null as User | null, login, logout };
}
type AuthContext = ReturnType<typeof useAuth>;
Parameters<T>
Извлекает типы параметров функции в виде кортежа:
function createUser(name: string, age: number, role: 'admin' | 'user') {
// ...
}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, role: 'admin' | 'user']
// Используем первый параметр
type FirstParam = Parameters<typeof createUser>[0]; // string
Awaited<T>
Рекурсивно разворачивает тип Promise:
type Result = Awaited<Promise<Promise<string>>>; // string
async function fetchUser(): Promise<User> { /* ... */ }
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>; // User
Практические комбинации
Частичное обновление с защитой ключей
type UpdatePayload<T> = Partial<Omit<T, 'id' | 'createdAt'>>;
interface Post {
id: number;
title: string;
content: string;
authorId: number;
createdAt: Date;
}
function updatePost(id: number, payload: UpdatePayload<Post>) {
// payload не может содержать id или createdAt
}
updatePost(1, { title: 'Новый заголовок' }); // OK
updatePost(1, { id: 999 }); // Ошибка
Глубокое readonly для состояния
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
Частые ошибки
Partialне делает вложенные объекты частичными.Partial<{ user: { name: string } }>сделаетuserнеобязательным, ноnameвнутри него останется обязательным. Для вложенной частичности нужен рекурсивный тип.Путаница
PickиOmit.Pick— белый список (берём только эти),Omit— чёрный список (берём всё кроме этих). При большом количестве полейOmitкомпактнее.Record<string, T>не гарантирует наличие ключа. При обращенииrecord[key]TypeScript не знает, есть ли этот ключ. Используйте--noUncheckedIndexedAccessили проверяйте наличие черезin.ReturnTypeс перегрузками. Для перегруженных функцийReturnTypeвернёт тип последней перегрузки. Это ограничение TypeScript.
Часто задаваемые вопросы
Можно ли комбинировать несколько Utility Types?
Да, это распространённая практика: Partial<Omit<User, 'id'>>, Readonly<Record<string, User[]>>.
Чем Omit отличается от Exclude?
Omit работает с ключами объектного типа (удаляет свойства). Exclude работает с union-типами (удаляет варианты из объединения).
Как создать свой Utility Type?
Используя keyof, in, условные типы и infer. Например, DeepPartial для рекурсивной частичности или Nullable<T> = T | null.
Влияют ли Utility Types на производительность в runtime?
Нет. Все типы TypeScript стираются при компиляции. Utility Types — это чисто компиляционный инструмент без влияния на JavaScript-код.
Заключение
Utility Types в TypeScript — это мощный инструмент для создания новых типов на основе существующих. Правильное применение Partial, Pick, Omit, Record и других утилит помогает избежать дублирования типов и делает код более выразительным и безопасным.
Для углублённого изучения TypeScript рекомендуем курс TypeScript с нуля. В первых модулях доступно бесплатное содержание, что позволяет познакомиться с форматом обучения до покупки полного доступа.
Постройте личный план изучения Typescript до уровня Middle — бесплатно!
Typescript — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Typescript
Лучшие курсы по теме

TypeScript с нуля
Антон Ларичев
CSS Flexbox
Антон Ларичев