TS Utility Types
Utility Types — встроенные в TypeScript generic-типы, которые преобразуют существующие типы: делают поля опциональными, только для чтения, выбирают подмножество свойств или исключают их.
Зачем нужно
- Позволяют переиспользовать один базовый тип в разных формах без дублирования кода
- Заменяют ручное написание производных интерфейсов:
Partial<User>вместо{ id?: number; name?: string; ... } - Типы DTO, формы, патч-запросы, конфиги — всё это производные от одного источника истины
Где используется
Partial<T>— формы редактирования, PATCH-запросыRequired<T>— проверка что все поля заполнены перед отправкойReadonly<T>— иммутабельные конфиги, props в ReactPick<T, K>иOmit<T, K>— DTO, публичные API-контрактыRecord<K, V>— словари, маппинги, индексные объектыReturnType<F>,Parameters<F>— вывод типов из существующих функцийAwaited<T>— тип результата промиса
Основной контент
Partial и Required
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Все поля опциональны
type UserPatch = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }
function updateUser(id: number, patch: Partial<User>): void {
// PATCH /api/users/:id
}
updateUser(1, { name: "Alice" }); // OK — частичное обновление
// Все поля обязательны
type FullUser = Required<User>;
// { id: number; name: string; email: string; age: number }
Readonly
type Config = Readonly<{
apiUrl: string;
timeout: number;
}>;
const config: Config = { apiUrl: "https://api.example.com", timeout: 5000 };
config.apiUrl = "other"; // Ошибка! Cannot assign to 'apiUrl' — read-only
// React props — обычно Readonly
type ButtonProps = Readonly<{
label: string;
onClick: => void;
}>;
Pick и Omit
interface Article {
id: number;
title: string;
body: string;
authorId: number;
createdAt: Date;
updatedAt: Date;
}
// Только нужные поля для превью
type ArticlePreview = Pick<Article, "id" | "title" | "createdAt">;
// { id: number; title: string; createdAt: Date }
// Убрать служебные поля для формы создания
type CreateArticleDto = Omit<Article, "id" | "createdAt" | "updatedAt">;
// { title: string; body: string; authorId: number }
Record
type Status = "active" | "inactive" | "banned";
type StatusLabel = Record<Status, string>;
const labels: StatusLabel = {
active: "Активен",
inactive: "Неактивен",
banned: "Заблокирован",
// пропустить ключ — ошибка компилятора
};
// Словарь пользователей по id
type UserMap = Record<number, User>;
const users: UserMap = { 1: { id: 1, name: "Alice", email: "a@b.com", age: 25 } };
Exclude и Extract
type EventType = "click" | "hover" | "focus" | "blur";
// Убрать варианты из union
type MouseEvents = Exclude<EventType, "focus" | "blur">;
// "click" | "hover"
// Оставить только совпадающие
type FocusEvents = Extract<EventType, "focus" | "blur">;
// "focus" | "blur"
ReturnType, Parameters, Awaited
function createUser(name: string, email: string): { id: number; name: string } {
return { id: Math.random, name };
}
type CreatedUser = ReturnType<typeof createUser>;
// { id: number; name: string }
type CreateUserArgs = Parameters<typeof createUser>;
// [name: string, email: string]
// Awaited — тип разрешённого промиса
async function fetchData: Promise<{ items: string }> {
return { items: ["a", "b"] };
}
type FetchResult = Awaited<ReturnType<typeof fetchData>>;
// { items: string }
NonNullable
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // User
function processUser(user: MaybeUser): void {
if (user == null) return;
const u: NonNullable<typeof user> = user; // u: User
}
Частые ошибки
- Путать
Partialи опциональность —Partial<T>делает поля опциональными, но не убирает их из типа - Использовать
Omitс несуществующими ключами — TypeScript не всегда даёт ошибку при неверном ключе вOmit Record<string, T>вместоRecord<LiteralUnion, T>— теряется проверка полноты ключей- Не использовать
Awaited— написатьReturnType<typeof fn>вместоAwaited<ReturnType<typeof fn>>для async-функции