В данной публикации рассмотрим как можно сократить количество типов/интерфейсов в Typescript, сделать их более лаконичными и сэкономить время себе и своей команде. Все это будем делать с помощью Util Types - специальных типов, которые предоставляет Typescript.

Что такое Util Types?
TypeScript предоставляет утилиты для облегчения преобразования типов. Данные типы доступны глобально, они добавляют гибкости в создание типов и помогают соблюдать принцип DRY.
Опциональные поля в типах - Partial
Допустим, что нам нужно сделать все поля в структуре опциональными:
interface UserData { uuid: string; username: string; age: number; } // Реализация такого типа ручками: interface OptionalUserData { uuid?: string; username?: string; age?: number; }
Трансформацию как в OptionalUserData можно сделать с помощью специального типа Partial<T>. Все Util Types содержат в себе дженерик, в который мы передаем тип, который нам нужно преобразовать.
Partial<T> преобразует все поля в типе и делает их опциональными:
interface UserData { uuid: string; username: string; age: number; } type OptionalUserData = Optional<UserData>; // Эквивалентно интерфейсу OptionalUserData, что был в листинге до этого
Преобразование всех полей в обязательные - Required
Required является противоположностью Partial - он делает все поля обязательными.
interface UserData { uuid: string; username: string; age?: number; // Опциональное поле } type RequiredUserData = Required<UserData>; // Property 'age' is missing in type '{ age: number; }' but required in type 'RequiredUserData'. const person: RequiredUserData = { uuid: "fff", username: "tokiory" };
Создание строготипизированных объектов - Record
Record используется для того чтобы создавать строготипизированные объекты.
Тип
Tнужен для того чтобы указать тип ключа для объекта;Тип
Uуказывает на тип значения поля.
interface PetInfo { age: number; breed: string; } type CatName = "lucas" | "oiko" | "jean"; const cats: Record<CatName, CatInfo> = { lucas: { age: 1, breed: "Maine Coon" }, oiko: { age: 15, breed: "Persian" }, jean: { age: 16, breed: "British Shorthair" }, };
Важно
Если указать вместо CatName в примере выше string, то у вас пропадет Intellisense для данного объекта в редакторе. Дело в том что Typescript будет думать, что внутрь объекта с типом Record<string, ...> можно положить любую строку в виде ключа и не будет анализировать те ключи, которые вы предоставили внутри объекта.
Делаем типизированные значения с автодополнением
Если мы просто создадим Record<string, ...>, то автодополнения нам не видать.
Typescript как уже было сказано выше будет считать, что Record<string, ...> может иметь любое поле, что попадает под тип string:
interface PersonInfo { isOnline: boolean; age: number; } type PersonalInfo = Record<string, PersonInfo>; const persons: PersonalInfo = { john12: { isOnline: true, age: 20 }, tokiory: { isOnline: false, age: 20 }, }; // Если мы напишем "persons." - то Intellisense не поймет чего мы от него хотим
Вместо этого подхода мы должны использовать оператор satisfies, который ввели в Typescript 4.9.
interface PersonInfo { isOnline: boolean; age: number; } type PersonalInfo = Record<string, PersonInfo>; const persons = { john12: { isOnline: true, age: 20 }, tokiory: { isOnline: false, age: 20 }, } satisfies PersonalInfo; // Если мы напишем "persons." - то Intellisense заработает!!!
Идея состоит в том, что оператор satisfies будет проверять каждое свойство и значение из persons на совместимость с исходными типами в PersonalInfo.
Например, поле john12 является частным случаем типа string, satisfies видит это
и понимает что ключ объекта удовлетворяет тому, что все ключи должны быть типом string и не выводит никакой ошибки.
Важно отметить, что в отличии от первого подхода, второй - только проверяет совместимость с исходными типами, сами
типы объекту persons не назначены, то есть persons имеет тип:
const persons: {john12: {isOnline: boolean, age: number}, tokiory: {isOnline: boolean, age: number}}
Типы для функций - Parameters и ReturnType
Данные типы используются для того чтобы извлечь типы из функций.
Parameters<T>- возвращает массив типов параметров в функции;ReturnType<T>- возвращает возвращаемый тип функции.
function capitalize({line}: {line: string}): string { return s.length ? s[0] + s.slice(1) : s; } type CapitalizeFuncArgs = Parameters<typeof capitalize>; // [ { line: string } ] type CapitalizeFuncReturn = ReturnType<typeof capitalize>; // string
Работа с полями интерфейса - Pick и Omit
Pick и Omit в основном используются с интерфейсами для удобной работы с полями.
Pick<T, U>- позволяет вернуть одно или несколько полей из интерфейса;Omit<T, U>- позволяет вернуть весь интерфейс без определенных полей.
interface Person { name: string; surname: string; age: number; login: string; email: string; } type LoginInfo = Pick<Person, "login" | "email" | "name">; // => { login: string; email: string; name: string; } type ShowName = Omit<Person, "login" | "email">; // => { name: string; surname: string; age: number; }
Общие элементы и уникальные элементы - Extract и Exclude
Extract- возвращает все типы, которые есть и в одном, и в другом переданом союзе типов;Exclude- возвращает все типы, которые есть или в первом, или в другом переданом союзе типов;
type MaleName = "Carl" | "Christian" | "Daren"; type FemaleName = "July" | "Daren" | "Ann"; type OnlyMaleName = Exclude<MaleName, FemaleName>; // "Carl" | "Christian" type OnlyGeneralName = Extract<MaleName, FemaleName>; // "Daren"
Взаимодействие с типами строк
Внизу предоставлены типы для взаимодействия со строковыми типами:
Uppercase<StringType>- Тип который преобразует все символы в верхний регистр;Lowercase<StringType>- Тип который преобразует все символы в нижний регистр;Capitalize<StringType>- Тип который преобразует первый символ в верхний регистр;Uncapitalize<StringType>- Тип который преобразует первый символ в нижний регистр.
type Cat = "cAt"; type UpperCat = Uppercase<Cat>; // "CAT" type LowerCat = Lowercase<Cat>; // "cat" type CapitalCat = Capitalize<Cat>; // "CAt" type UncapitalCat = Uncapitalize<Cat>; // "cAt"
Если вы передаете данным союзный тип, то Typescript обработает тип для каждого элемента союза:
type Pet = "doG" | "CaT"; type UpperPet = Uppercase<Pet>; // "DOG" | "CAT" type LowerPet = Lowercase<Pet>; // "dog" | "cat" type CapitalPet = Capitalize<Pet>; // "DoG" | "CaT" type UncapitalPet = Uncapitalize<Pet>; // "doG" | "caT"
Вместо заключения ?
Если вам понравилась данная статья - то вы всегда можете перейти в мой блог, там больше схожей информации о веб-разработке.
Если у вас остались вопросы - не стесняйтесь задавать их в комментариях. Хорошего времяпрепровождения! ??♂
