Skip to Content
TypeScriptУсловные и утилитарные типы

Условные и утилитарные типы

Уровень: senior ближе, чем middle. На middle хватает готовых утилит и понимания, что они делают.

Вопрос

Что делают Partial, Pick, Omit, Record, ReturnType, и как устроен свой условный тип?

Короткий ответ

  • Partial<T> делает все поля необязательными.
  • Required<T> делает их обязательными.
  • Pick<T, K> оставляет перечисленные ключи.
  • Omit<T, K> убирает перечисленные ключи.
  • Record<K, V> строит объект с ключами K и значениями V.
  • Exclude<T, U> убирает из union те члены, что совместимы с U.
  • Extract<T, U> оставляет только совместимые.
  • ReturnType<F> и Parameters<F> достают типы из функции.

Условный тип выглядит как T extends U ? X : Y. infer объявляет переменную типа внутри ветки extends:

type ElementOf<T> = T extends (infer Item)[] ? Item : never

ElementOf<string[]> это string.

Разбор

Partial удобен для патча, но опасен для пропсов компонента: случайно необязательным становится и id, без которого экран не живёт. Точечный Pick<User, 'name' | 'email'> честнее.

keyof T даёт union ключей. Вместе с индексом T[K] получается тип значения. Распределённые условные типы раскрываются по каждому члену union отдельно: ToArray<string | number> при T extends unknown ? T[] : never даёт string[] | number[], а не (string | number)[]. Кортеж из одного параметра, [T] extends [unknown], распределение отключает.

На что смотрят на senior

Свой условный тип пишут, когда он убирает повтор в публичном API. Если им пользуются один раз внутри файла, проще назвать конкретный тип руками.

infer в рекурсии по глубоким объектам легко сделать нечитаемым. На разборе кода плюс получает тот, кто может объяснить тип на примере одного вызова, а не только на синтаксисе.

Template literal types, `on${Capitalize<string>}`, встречаются в обёртках над событиями. Их стоит узнать, но не строить всю модель приложения на строковых шаблонах.

Типичные ошибки

  • Размазывать Partial<Props> на компонент и ловить undefined в обязательных полях.
  • Путать Exclude для union и Omit для ключей объекта.
  • Ждать, что условный тип исполнится в рантайме. Это стирается при компиляции, в JS его нет.

Задача

Чем Omit<User, 'id'> отличается от Exclude<User, 'id'> для такого типа?

type User = { id: string; name: string }
Показать ответ

Omit убирает ключи объекта. Результат { name: string }.

Exclude работает с union, не с ключами. User не union из 'id', поэтому Exclude<User, 'id'> останется User: объект не совместим с строкой 'id' и не выкидывается.

Для union ролей нужен Exclude<Role, 'guest'>, для полей формы — Omit.

Last updated on