Хотите заказать веб-сайт? Связаться с нами

Partial, Pick и Omit: работа с типами в TS

Иногда подходящего типа под задачу не существует, но переписывать интерфейс с нуля — плохая идея: он быстро разойдётся с оригиналом. В этом уроке разберём три утилитарных типа TypeScript — Partial, Pick и Omit. Они позволяют собирать новые типы на основе уже существующих, не дублируя код и сохраняя связь между связанными сущностями.

Partial, Pick и Omit: работа с типами в TS

Зачем нужны утилитарные типы

Когда в проекте есть один центральный тип, например User или Event, разные части приложения часто нуждаются не в полной его версии, а в урезанной или изменённой. Форма для черновика события не должна требовать id, который ещё не сгенерирован. Функция обновления пользователя должна принимать не весь объект User, а только те поля, которые реально меняются.

Можно, конечно, вручную описать под каждый такой случай отдельный интерфейс. Но тогда при добавлении нового поля в основной тип придётся не забыть обновить все производные типы вручную — а это источник ошибок. Утилитарные типы решают эту проблему: они выводят новый тип автоматически, опираясь на структуру исходного. Изменился оригинал — изменились и все типы, построенные на его основе.

Partial, Pick и Omit входят в набор встроенных утилитарных типов TypeScript и доступны из коробки, без установки дополнительных пакетов. Внутри они устроены на дженериках и операторе keyof, которые уже встречались в курсе раньше, — поэтому, по сути, это готовые шаблоны, которые применяют уже знакомые концепции к частой практической задаче.

Partial делает все свойства опциональными

Как устроен Partial

Partial<T> принимает тип T и возвращает новый тип, в котором каждое свойство становится необязательным — TypeScript как будто добавляет знак вопроса после имени каждого поля. Это особенно полезно там, где объект передаётся частями, а не целиком.

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}
 
function updateUser(id: string, changes: Partial<User>): void {
  // changes может содержать любое подмножество полей User,
  // включая пустой объект
}
 
updateUser("u1", { name: "Иван" });
updateUser("u1", { email: "ivan@example.com", age: 29 });

Без Partial пришлось бы объявлять отдельный тип с теми же полями, но с вопросительными знаками у каждого — и следить, чтобы он не разошёлся с User при любых изменениях основного интерфейса.

Когда использовать Partial

  1. Функции обновления объектов, куда передают только изменённые поля
  2. Формы, которые сохраняют черновик с неполными данными
  3. Объединение настроек по умолчанию с пользовательскими переопределениями

Не создавайте отдельный интерфейс с необязательными полями вручную, если он на самом деле описывает частичное обновление существующего типа. Используйте Partial<T> — так оба типа гарантированно останутся синхронизированы при изменении оригинала.

Pick выбирает нужные свойства

Синтаксис Pick

Pick<T, K> берёт тип T и список ключей K, а возвращает новый тип, который содержит только перечисленные свойства. Ключи должны быть частью keyof T, поэтому опечатку в названии поля TypeScript сразу подсветит как ошибку.

interface Event {
  id: string;
  title: string;
  description: string;
  host: string;
  rsvps: string[];
}
 
type EventPreview = Pick<Event, "id" | "title" | "host">;
 
const preview: EventPreview = {
  id: "e1",
  title: "TypeScript Meetup",
  host: "Anjana",
};

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

Pick удобен там, где нужен «облегчённый» вариант типа: карточка в списке событий, короткий профиль пользователя в шапке сайта, элемент выпадающего меню. Вместо того чтобы заново перечислять три поля из пяти, достаточно указать Pick<Event, "id" | "title" | "host">, и TypeScript сам подтянет типы этих полей из Event. Если позже в Event изменится тип поля title, EventPreview автоматически подстроится под это изменение.

Важная деталь: список ключей во втором параметре Pick — это union из строковых литералов, а не массив и не отдельный тип.

Если попытаться передать ключ, которого нет в исходном интерфейсе, TypeScript выдаст ошибку компиляции ещё до запуска кода. Это удобно использовать как своеобразную документацию: глядя на объявление EventPreview, сразу понятно, какие именно поля из Event реально нужны в конкретном месте приложения, без необходимости открывать сам интерфейс Event.

Omit исключает свойства

Синтаксис Omit

Omit<T, K> работает противоположно Pick: он берёт тип T и убирает из него перечисленные в K свойства, оставляя все остальные без изменений. Это удобно, когда новых полей нужно исключить меньше, чем оставить.

type NewEvent = Omit<Event, "id" | "rsvps">;
 
function createEvent(data: NewEvent): Event {
  return {
    id: crypto.randomUUID(),
    rsvps: [],
    ...data,
  };
}
 
const draft: NewEvent = {
  title: "Разбор дженериков",
  description: "Погружаемся в generic-типы",
  host: "Anjana",
};

Здесь id и rsvps заполняются автоматически внутри функции, поэтому от вызывающего кода их требовать не нужно. Omit убирает эти два поля из типа один раз, и TypeScript больше не позволит передать их снаружи по ошибке.

Pick vs Omit — что выбрать

Выбор между Pick и Omit обычно зависит от того, каких полей больше — тех, что нужно оставить, или тех, что нужно убрать.

Ситуация Что использовать
Нужно оставить одно-два поля из большого типа Pick
Нужно убрать одно-два поля, а остальные оставить как есть Omit
Нужно сделать все поля типа необязательными Partial

Комбинирование Partial, Pick и Omit

Главная сила утилитарных типов раскрывается, когда их применяют вместе. Ничто не мешает взять результат одного утилитарного типа и передать его в другой — TypeScript просто выведет итоговый тип пошагово.

type EventDraft = Partial<Pick<Event, "title" | "description" | "host">>;
 
function saveDraft(draft: EventDraft): void {
  console.log(draft);
}
 
saveDraft({ title: "Черновик события" });
saveDraft({});

Здесь сначала Pick сузил Event до трёх полей, а затем Partial сделал каждое из них необязательным. В результате получился тип для черновика, где можно сохранить хоть одно поле, хоть все три, хоть ничего — и при этом опечатка в имени поля или лишнее свойство останутся ошибкой компиляции.

Если после применения Omit в типе остаётся меньше полей, чем вы исключили, это сигнал пересмотреть подход: обычно короче и понятнее описать нужный набор через Pick, перечислив то, что должно остаться, а не то, что нужно убрать.

Итоги

Partial, Pick и Omit — три утилитарных типа, которые решают одну и ту же задачу разными способами: позволяют строить новый тип на основе уже существующего, не копируя его структуру вручную. Partial делает свойства необязательными, Pick оставляет только нужные поля, а Omit убирает лишние. Использовать их вместе — обычная практика в реальных проектах на TypeScript, особенно при работе с формами, черновиками и частичными обновлениями данных.

В следующем уроке разберём тип any и комментарий @ts-ignore — крайние случаи, когда строгая типизация мешает работе, и как аккуратно ими пользоваться, не теряя пользы от TypeScript.

Теги: