ДокументацияTypeScriptUtility Types в TypeScript
Средний 12 мин чтения

Utility Types в TypeScript

Встроенные утилитарные типы TypeScript — Partial, Required, Pick, Omit, Record, Exclude, Extract, ReturnType и другие. Примеры использования и практические паттерны.

typescriptutility typesPartialPickOmitRecord

Что такое Utility Types

Utility Types — встроенные типы-преобразователи. Они берут существующий тип и возвращают его модифицированную версию. Не нужно писать свои — TypeScript предоставляет готовый набор.

Partial

Делает все свойства необязательными:

interface User {
  name: string
  age: number
  email: string
}

type PartialUser = Partial<User>
// { name?: string; age?: number; email?: string }

Практическое применение — функция обновления, где можно передать любые поля:

function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates }
}

updateUser(user, { name: 'Новое имя' })
updateUser(user, { age: 26, email: 'new@mail.ru' })

Required

Обратное Partial — делает все свойства обязательными:

interface Config {
  host?: string
  port?: number
  debug?: boolean
}

type StrictConfig = Required<Config>
// { host: string; port: number; debug: boolean }

Readonly

Делает все свойства только для чтения:

interface Point {
  x: number
  y: number
}

type ReadonlyPoint = Readonly<Point>
// { readonly x: number; readonly y: number }

const point: ReadonlyPoint = { x: 0, y: 0 }
point.x = 5 // Ошибка

Pick<T, K>

Выбирает только указанные свойства из типа:

interface User {
  id: number
  name: string
  email: string
  password: string
  avatar: string
}

type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>
// { id: number; name: string; avatar: string }

Можно выбрать и одно поле:

type UserId = Pick<User, 'id'> // { id: number }

Omit<T, K>

Исключает указанные свойства — противоположность Pick:

type UserSafe = Omit<User, 'password'>
// { id: number; name: string; email: string; avatar: string }

Частый паттерн при создании сущности — исключить id, потому что он генерируется на сервере:

type CreateUser = Omit<User, 'id'>

function create(data: CreateUser): Promise<User> {
  return fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(data),
  }).then(r => r.json())
}

Record<K, V>

Создаёт тип объекта с ключами K и значениями V:

type UserRole = 'admin' | 'editor' | 'viewer'

const roleLabels: Record<UserRole, string> = {
  admin: 'Администратор',
  editor: 'Редактор',
  viewer: 'Читатель',
}

Если забыть одну из ролей — TypeScript укажет на ошибку:

const bad: Record<UserRole, string> = {
  admin: 'Администратор',
  // Ошибка: отсутствуют 'editor' и 'viewer'
}

Exclude<T, U>

Исключает из union-типа T те варианты, которые входят в U:

type AllTypes = string | number | boolean | null
type WithoutNull = Exclude<AllTypes, null>
// string | number | boolean

Практический пример:

type Status = 'loading' | 'success' | 'error' | 'idle'
type ActiveStatus = Exclude<Status, 'idle'>
// 'loading' | 'success' | 'error'

Extract<T, U>

Извлекает из union-типа T те варианты, которые входят в U:

type T = string | number | boolean
type OnlyStringOrNumber = Extract<T, string | number>
// string | number

NonNullable

Исключает null и undefined из типа:

type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString>
// string

ReturnType

Извлекает тип возвращаемого значения функции:

function getUser() {
  return { id: 1, name: 'Анна', email: 'anna@mail.ru' }
}

type User = ReturnType<typeof getUser>
// { id: number; name: string; email: string }

Это особенно удобно, когда функция возвращает сложный объект, а вы не хотите дублировать тип:

function createConfig() {
  return {
    api: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
    headers: { Authorization: '' },
  }
}

type Config = ReturnType<typeof createConfig>

Parameters

Извлекает типы параметров функции в виде кортежа:

function createUser(name: string, age: number, active: boolean) {
  // ...
}

type UserParams = Parameters<typeof createUser>
// [string, number, boolean]

Можно получить конкретный параметр по индексу:

type NameParam = Parameters<typeof createUser>[0] // string

Awaited

Раскрывает тип из Promise. Появился в TypeScript 4.5:

type ResolvedUser = Awaited<Promise<User>>
// User

type Nested = Awaited<Promise<Promise<string>>>
// string (раскрывает все уровни)

Полезно для типизации результатов async-функций:

async function fetchUsers() {
  const res = await fetch('/api/users')
  return res.json() as Promise<User[]>
}

type Users = Awaited<ReturnType<typeof fetchUsers>>
// User[]

NoInfer

Запрещает TypeScript выводить тип параметра. Появился в TypeScript 5.4:

function createRouter<T extends string>(routes: T[], basePath: T) {
  // ...
}

// Без NoInfer TypeScript может вывести basePath как любой string
// С NoInfer basePath должен совпадать с одним из routes

Комбинирование Utility Types

Utility Types можно комбинировать для создания сложных производных типов:

interface Article {
  id: number
  title: string
  content: string
  author: string
  tags: string[]
  createdAt: Date
  updatedAt: Date
}

type CreateArticle = Omit<Article, 'id' | 'createdAt' | 'updatedAt'>
// Для POST-запроса — без auto-generated полей

type UpdateArticle = Partial<Omit<Article, 'id'>>
// Для PATCH-запроса — можно обновить любые поля, кроме id

type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>
// Для списка — только нужные поля

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

Типобезопасный Form:

interface FormData {
  username: string
  password: string
  remember: boolean
}

type FormErrors = Partial<Record<keyof FormData, string>>
// { username?: string; password?: string; remember?: string }

type FormState = {
  values: FormData
  errors: FormErrors
  touched: Partial<Record<keyof FormData, boolean>>
}

Типобезопасный API-client:

interface ApiRoutes {
  '/users': {
    GET: { response: User[]; query?: { page: number } }
    POST: { response: User; body: CreateUser }
  }
  '/users/:id': {
    GET: { response: User; params: { id: string } }
    PUT: { response: User; params: { id: string }; body: UpdateUser }
    DELETE: { response: void; params: { id: string } }
  }
}

type CreateUser = Omit<User, 'id'>
type UpdateUser = Partial<Omit<User, 'id'>>

DeepPartial (кастомный на основе идей Utility Types):

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

interface Config {
  database: {
    host: string
    port: number
    credentials: {
      user: string
      password: string
    }
  }
  cache: {
    enabled: boolean
    ttl: number
  }
}

type PartialConfig = DeepPartial<Config>
// Все свойства на всех уровнях — optional

Шпаргалка

Utility TypeЧто делает
Partial<T>Все свойства optional
Required<T>Все свойства обязательные
Readonly<T>Все свойства readonly
Pick<T, K>Выбрать свойства
Omit<T, K>Исключить свойства
Record<K, V>Объект с ключами K и значениями V
Exclude<T, U>Исключить из union
Extract<T, U>Извлечь из union
NonNullable<T>Убрать null/undefined
ReturnType<T>Тип возврата функции
Parameters<T>Типы параметров функции
Awaited<T>Раскрыть Promise

Итог

Utility Types — готовые инструменты для преобразования типов без ручного дублирования. Самые используемые: Partial для обновлений, Pick и Omit для проекций, Record для словарей, ReturnType для извлечения типа из функции. Комбинируя их, можно описать любой производный тип в одну строку.