Служебные типы это встроенные в TypeScript обобщённые типы, которые превращают один тип в другой. Вместо второго типа User со всеми необязательными полями вы пишете Partial<User>; вместо копирования трёх полей Pick<User, "id" | "name">. Они глобальные, поэтому импорт не нужен.
Когда меняется User, все четыре производных типа следуют за ним. Стандартная библиотека TypeScript (lib.es5.d.ts) объявляет 22 служебных типа. Ниже перечислены все они, сгруппированные по виду типов, с которыми работают, со ссылкой на подробную страницу там, где она есть.
Объектные типы: Partial, Required, Readonly, Pick, Omit, Record
| Служебный тип | Что делает | Пример |
|---|---|---|
Partial<T> | делает каждое свойство необязательным | Partial<User> для данных обновления |
Required<T> | делает каждое свойство обязательным (убирает ?) | Required<Config> после применения значений по умолчанию |
Readonly<T> | делает каждое свойство readonly | Readonly<State> |
Pick<T, K> | оставляет только ключи K | Pick<User, "id" | "name"> |
Omit<T, K> | убирает ключи K | Omit<User, "password"> |
Record<K, V> | объектный тип с ключами K и значениями V | Record<"en" | "de", string> |
Required<T> противоположен Partial<T>; страница о Partial описывает оба, включая явное undefined, которое пропускает такое развёртывание.
Типы объединения: Exclude, Extract, NonNullable
| Служебный тип | Что делает | Пример |
|---|---|---|
Exclude<U, M> | убирает члены объединения, присваиваемые в M | Exclude<"a" | "b" | "c", "a"> это "b" | "c" |
Extract<U, M> | оставляет члены объединения, присваиваемые в M | Extract<string | number, number> это number |
NonNullable<T> | убирает null и undefined | NonNullable<string | null> это string |
Эти три работают с объединениями, а не с объектами. В этом ключевое отличие от Pick и Omit, которые принимают объектный тип и список его ключей.
Типы функций и классов: Parameters, ReturnType и другие
| Служебный тип | Что делает | Пример |
|---|---|---|
ReturnType<F> | тип возврата типа функции | ReturnType<typeof createStore> |
Parameters<F> | типы параметров в виде кортежа | Parameters<typeof fetchPage>[0] |
ConstructorParameters<C> | параметры конструктора класса в виде кортежа | ConstructorParameters<typeof Point> |
InstanceType<C> | тип экземпляра, который создаёт конструктор | InstanceType<typeof Point> |
ThisParameterType<F> | тип параметра this функции | ThisParameterType<typeof greet> |
OmitThisParameter<F> | тип функции без параметра this | тип greet.bind(obj) |
ThisType<T> | задаёт тип this внутри методов объектного литерала | используется с noImplicitThis в API-билдерах |
NoInfer<T> | запрещает выводить параметр типа из этой позиции | fallback: NoInfer<C> |
typeof createOrder нужен потому, что эти утилиты принимают тип, а createOrder это значение. С классами то же самое: typeof Point это тип конструктора, а просто Point как тип уже означает тип экземпляра.
NoInfer управляет тем, откуда дженерик берёт свой тип:
Без NoInfer TypeScript вывел бы C из обоих аргументов и расширил бы его до "red" | "green" | "blue", так что опечатка в запасном значении была бы принята.
Строковые типы: Uppercase, Lowercase, Capitalize, Uncapitalize
| Служебный тип | Что делает | Пример |
|---|---|---|
Uppercase<S> | переводит строковый литеральный тип в верхний регистр | Uppercase<"get"> это "GET" |
Lowercase<S> | переводит его в нижний регистр | Lowercase<"GET"> это "get" |
Capitalize<S> | делает первый символ заглавным | Capitalize<"name"> это "Name" |
Uncapitalize<S> | делает первый символ строчным | Uncapitalize<"Name"> это "name" |
Эти четыре встроены в компилятор, а не написаны на TypeScript, и полезнее всего внутри шаблонных литеральных типов, например `on${Capitalize<E>}` для имён обработчиков событий.
Промисы: Awaited
| Служебный тип | Что делает | Пример |
|---|---|---|
Awaited<T> | тип, который получается из await, с разворачиванием вложенных промисов | Awaited<Promise<Promise<number>>> это number |
Awaited<ReturnType<typeof fn>> это стандартный способ назвать тип результата асинхронной функции, не объявляя его отдельно.
Сочетание служебных типов
Служебные типы вкладываются друг в друга. Несколько сочетаний встречаются так часто, что их стоит знать наизусть:
Читайте вложенный служебный тип изнутри наружу: Readonly<Pick<Post, "id" | "title">> сначала оставляет два свойства, а затем делает их доступными только для чтения. Из тех же частей строится помощник PartialBy, который делает необязательными только некоторые ключи; он выписан на странице о Partial.
Служебные типы ничего не делают во время выполнения
Каждый служебный тип стирается при компиляции кода. Значение с типом Omit<User, "password"> во время выполнения всё равно может нести пароль, если он был в исходном объекте:
Тип только ограничивает то, что вашему коду разрешено читать. Чтобы удалить поле из данных, уберите его деструктуризацией, как в последних строках, а чтобы запретить изменения во время выполнения, используйте Object.freeze, а не Readonly. Встроенные типы это однострочные сопоставленные и условные типы, так что теми же средствами можно написать и свои.
Часто задаваемые вопросы
Что такое служебные типы в TypeScript?
Обобщённые типы, которые поставляются с TypeScript и преобразуют другие типы: Partial<T> делает каждое свойство необязательным, Pick<T, K> оставляет часть свойств, ReturnType<F> получает тип возврата функции и так далее. Они объявлены в стандартной библиотеке, поэтому используются без импорта.
Нужно ли импортировать служебные типы?
Нет. Partial, Omit, Record, ReturnType и остальные это глобальные типы из встроенных файлов библиотеки TypeScript. Пишите Partial<User> где угодно; не нужны ни импорт, ни пакет npm.
Какие служебные типы встроены в TypeScript?
22, все объявлены в lib.es5.d.ts: Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ConstructorParameters, ReturnType, InstanceType, ThisParameterType, OmitThisParameter, ThisType, NoInfer, Awaited, Uppercase, Lowercase, Capitalize и Uncapitalize.
Меняют ли служебные типы объекты во время выполнения?
Нет. Они только описывают типы и стираются из вывода JavaScript. Omit<User, "password"> не удаляет свойство password, а Readonly<T> ничего не замораживает. Чтобы изменить сам объект, напишите код: деструктуризацию с rest, Object.freeze и так далее.
Можно ли написать собственные служебные типы?
Да. Встроенные это обычный TypeScript: большинство из них это однострочные сопоставленные или условные типы в lib.es5.d.ts. type Nullable<T> = { [K in keyof T]: T[K] | null } это собственный служебный тип, написанный тем же способом.