Перейти к основному содержимому

Сопоставленные типы в TypeScript

Сопоставленный тип строит новый тип, проходя по каждому свойству существующего типа и изменяя его одинаковым образом.

  • Сопоставленные типы = преобразуют каждое свойство типа
  • Часто используемые встроенные: Partial, Readonly, Pick, Omit, Record

Небольшой пример сопоставленного типа

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

script.ts
// Small example
type Person = { name: string; age: number };
type PartialPerson = { [P in keyof Person]?: Person[P] };
type ReadonlyPerson = { readonly [P in keyof Person]: Person[P] };

Базовый синтаксис сопоставленного типа

Основной синтаксис сопоставленного типа — { [P in K]: T }, где:

  • P — имя свойства, которое обрабатывается в данный момент
  • K — объединение имён свойств, по которым нужно пройти (часто keyof SomeType)
  • T — тип, который каждое свойство получает в новом типе

Пример

PartialPerson добавляет ? к каждому свойству типа Person, поэтому объект только со свойством name допустим. ReadonlyPerson добавляет readonly к каждому свойству, поэтому присваивание значения свойству age после этого было бы ошибкой компиляции.

Prompt
Покажите основной синтаксис сопоставленного типа { [P in K]: T }, преобразовав интерфейс вручную. Объявите интерфейс Person со свойствами name: string, age: number и email: string. Постройте сопоставленный тип PartialPerson, который делает все свойства необязательными, и создайте объект partialPerson только со свойством name: "John". Затем постройте сопоставленный тип ReadonlyPerson, который помечает каждое свойство как readonly, создайте полный объект readonlyPerson (Alice, 30, alice@example.com) и оставьте закомментированное присваивание age с пояснением, что это была бы ошибка записи в свойство только для чтения. Ничего не выводится.
Вывод: программа на TypeScript и захваченный вывод

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

Стандартная библиотека TypeScript уже содержит несколько полезных сопоставленных типов, поэтому самые распространённые из них не нужно писать самому:

  • Partial<T>: сделать все свойства необязательными
  • Readonly<T>: сделать все свойства доступными только для чтения
  • Pick<T, K>: оставить только выбранный набор ключей
  • Omit<T, K>: удалить набор ключей
  • Record<K, V>: сопоставить набору ключей один тип значения

Пример

Каждый псевдоним ниже применяет к User один встроенный сопоставленный тип. Комментарий после каждого псевдонима показывает получающийся тип объекта. Также показан Required — он противоположен Partial.

Prompt
Покажите встроенные вспомогательные сопоставленные типы, применённые к одному интерфейсу. Объявите интерфейс User со свойствами id: number, name: string, email: string и isAdmin: boolean, затем создайте псевдонимы типов с помощью Partial<User>, Required<Partial<User>>, Readonly<User>, Pick<User, "email" | "id">, Omit<User, "id" | "isAdmin"> и Record<"admin" | "user" | "guest", string>, снабдив каждый комментарием, в котором расписан эквивалентный тип объекта. Файл только объявляет типы и ничего не выводит.
Вывод: программа на TypeScript и захваченный вывод

Создание собственных сопоставленных типов

Вы можете писать собственные обобщённые сопоставленные типы, чтобы преобразовывать типы именно так, как вам нужно.

Пример

StringifyProperties<T> превращает каждое свойство в string. Validator<T> превращает каждое свойство в функцию, которая принимает значение типа этого свойства (T[P]) и возвращает boolean, поэтому productValidator должен содержать по одной проверке на каждое свойство типа Product.

Prompt
Покажите два собственных обобщённых сопоставленных типа, построенных на интерфейсе Product (id: number, name: string, price: number, inStock: boolean). Напишите StringifyProperties<T>, который превращает каждое свойство в string, и Validator<T>, который превращает каждое свойство P в функцию (value: T[P]) => boolean. Примените первый, чтобы получить тип StringProduct, и с помощью второго постройте объект productValidator со следующими проверками: id больше 0, непустое name, price не меньше 0 и inStock является значением типа boolean. Ничего не выводится.
Вывод: программа на TypeScript и захваченный вывод

Изменение модификаторов свойств

Сопоставленный тип также может добавлять или удалять модификаторы свойств readonly и ? (необязательное свойство). Запись +readonly или readonly добавляет модификатор, а запись -readonly удаляет его. То же самое работает для ? с помощью -?.

Пример

Mutable<T> использует -readonly, чтобы убрать признак «только для чтения» у каждого свойства типа Configuration. RequiredProps<T> использует -?, чтобы сделать каждое необязательное свойство обязательным.

Prompt
Покажите, как сопоставленный тип может удалять модификаторы readonly и ? с помощью -readonly и -?. Объявите интерфейс Configuration со свойствами readonly apiKey: string, readonly apiUrl: string, timeout?: number и retries?: number. Напишите Mutable<T> (убирает readonly у каждого свойства) и RequiredProps<T> (убирает ? у каждого свойства), примените каждый к Configuration как MutableConfig и RequiredConfig и после каждого напишите комментарий с эквивалентным типом объекта. Файл только объявляет типы и ничего не выводит.
Вывод: программа на TypeScript и захваченный вывод

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

Сопоставленные типы становятся ещё мощнее, когда вы объединяете их с условными типами. Тогда новый тип каждого свойства может зависеть от прежнего.

Пример

FormattedResponse<T> проверяет каждое свойство с помощью T[P] extends number и превращает числовые свойства в string. StringPropsOnly<T> использует конструкцию as для переименования ключей: свойства, которые не являются строками, сопоставляются с never, что убирает их из результата.

Prompt
Объедините сопоставленные типы с условными типами на интерфейсе ApiResponse (data: unknown, status: number, message: string, timestamp: number). Напишите FormattedResponse<T>, который отображает каждое свойство типа number в string, а остальные оставляет без изменений, и StringPropsOnly<T>, который с помощью конструкции переименования ключей as оставляет только свойства с типом string. Примените оба типа к ApiResponse и прокомментируйте получившиеся типы. Ничего не выводится.
Вывод: программа на TypeScript и захваченный вывод

Ключевые выводы

Сопоставленные типы позволяют единообразно преобразовывать каждое свойство типа.

Ключевые понятия:

  • Преобразование типов: массовое изменение типов свойств
  • Модификаторы свойств: добавление или удаление readonly и ?
  • Переименование ключей: переименование или отбор свойств с помощью конструкции as
  • Композиция: сочетание сопоставленных типов с другими возможностями TypeScript, например с условными типами

Типичные сценарии использования:

  • Создание версий типов только для чтения
  • Превращение всех свойств в необязательные или обязательные
  • Преобразование типов свойств (например, в nullable или readonly)
  • Отбор свойств по их типам
  • Создание типобезопасных вспомогательных функций