Что такое utility types?

🎯 Зачем спрашивают
  • Проверить знание стандартных инструментов TypeScript, которые повседневно используют.
  • Убедиться, что кандидат понимает, что utility types — это просто generics + mapped types + conditional types под капотом, а не «магия».
  • Понять, умеет ли кандидат читать и писать собственные утилиты, т.е. насколько глубоко он понимает типовую систему TS.
 
📝 Ответ
Utility types — это набор уже готовых (generic) типов для различных типовых задач: трансформация, модификация и комбинирование типов. Популярные utility types:
  • Partial<T>
    • Делает все свойства типа T необязательными.
      type User = { id: number; name: string }; type PartialUser = Partial<User>; // { id?: number; name?: string }
  • Required<T>
    • Обратный к Partial — все свойства обязательные.
      type PartialUser = { id?: number; name?: string }; type FullUser = Required<PartialUser>; // { id: number; name: string }
  • Readonly<T>
    • Все свойства становятся только для чтения.
      type User = { id: number; name: string }; type ReadonlyUser = Readonly<User>; // { readonly id: number; readonly name: string }
  • Pick<T, K>
    • Создаёт тип только с выбранными свойствами.
      type User = { id: number; name: string; age: number }; type UserPreview = Pick<User, "id" | "name">; // { id: number; name: string }
  • Omit<T, K>
    • Противоположен Pick — исключает указанные свойства.
      type User = { id: number; name: string; age: number }; type UserWithoutAge = Omit<User, "age">; // { id: number; name: string }
  • Record<K, T>
    • Создаёт объектный тип, где ключи K имеют значения типа T.
      type Roles = "admin" | "user" | "guest"; type RolePermissions = Record<Roles, boolean>; // { admin: boolean; user: boolean; guest: boolean }
 
Так же вы можете создавать свои собственные utility types.
 
 
⚖️ Компромиссы
  • ✅ Плюсы: экономия времени, повышение читаемости, меньше boilerplate.
  • ❌ Минусы:
    • Чрезмерное увлечение утилитами может ухудшать читаемость (особенно вложенные конструкции).
    • Иногда проще описать новый тип, чем комбинировать 3 утилиты.
 
🔎 Встречные вопросы
  • Как реализован Partial<T> под капотом?
  • Чем Pick отличается от Omit?
  • В чём разница между Exclude и Omit?
  • Что такое «distributive conditional types» и почему Exclude работает именно так?
 
🚩 Красные флаги
  • Отвечают только «ну, это встроенные типы для TS».
  • Перечисляют только 1–2 (Partial, Pick) и не могут показать примеры.
  • Считают, что утилиты — это что-то «отдельное» от TypeScript generics (а на самом деле они просто «шорткаты» на conditional types + mapped types).
  • А зачем мне эти типы? Я просто копипастну тип, да и отредактирую
 
🛠 Практика
Реализовать MyPartial<T>
type User = { id: number; name: string }; type Test = MyPartial<User>; // { id?: number; name?: string }
Реализовать MyRequired<T>
type User = { id?: number; name?: string }; type Test = MyRequired<User>; // { id: number; name: string }
Реализовать MyReadonly<T>
type User = { id: number; name: string }; type Test = MyReadonly<User>; // { readonly id: number; readonly name: string }
Реализовать MyPick<T, K>
type User = { id: number; name: string; age: number }; type Test = MyPick<User, "id" | "name">; // { id: number; name: string }
Реализовать MyOmit<T, K>
type User = { id: number; name: string; age: number }; type Test = MyOmit<User, "age">; // { id: number; name: string }
Реализовать MyKeys<T>
type User = { id: number; name: string; age: number; }; type Keys = MyKeys<User>; // "id" | "name" | "age"
Реализовать DeepKeys<T>/
type User = { id: number; name: string; address: { city: string; street: string; }; }; type DeepKeys<User>; // "id" | "name" | "address" | "city" | "street"
 
📚 Источники / ссылки