Я всегда восхищаюсь мощью системы шрифтов в программировании. В последнее время TypeScript очаровал меня своими сверхспособностями.

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

Ящик для инструментов

Вот список сопоставителей типов, предоставляемых Typescript из коробки:

// Make all properties of T optional: ? operator
// `keyof T` defines a set, we can iterate keys by [Property in keyof T]
type Partial<T> = {
  [Property in keyof T]?: T[Property];
};
type SomePartial = Partial<{ name: string; }> // { name?: string | undefined }

// Make all properties of T required: -? operator
type Required<T> = {
  [Property in keyof T]-?: T[Property];
};
type SomeRequired = Required<{ name?: string; }> // { name: string }


// Make all properties of T readonly: readonly operator
type Readonly<T> = {
  readonly [Property in keyof T]: T[Property];
};
type SomeReadonly = Readonly<{ name: string; }> // { readonly name: string }

// Composed
type SomeComposed = Partial<Readonly<{ name: string }>> // { readonly name?: string | undefined }

Бонусный оператор -readonly сопоставляет типы с доступными для записи. (не входит в машинопись)

// Make all properties of T writable: -readonly operator
type Writable<T> = {
  -readonly [Property in keyof T]: T[Property];
};
type SomeWritable = Writable<{ readonly name: string; }> // { name: string }

Typescript 4.1 позволяет использовать переназначение ключей для изменения существующих ключей типа для создания нового.

// As classic map method, we can use `as operator` to map keys.
// [Property in (key of T -> as (map) -> NewProperties)]: Iterate mapped keys
type ReMapped<T> = {
  [Property in keyof T as NewProperties]: T[Property]
}

// Use template literal types to create new keys.
type Getter<T> = {
  [Property in keyof T as `get${Capitalize<string & Property>}`]: () => T[Property]
}
type SomeGetter = Getter<{ id: string }> // { getId: () => string }

// Filter out keys with Exclude
type ExcludeName<T> = {
  [Property in keyof T as Exclude<Property, 'name' | 'id'>]: T[Property]
}
type SomeNameExcluded = ExcludeName<{ name: string; age: number; id: string }> // { age: number }

// Pick keys with Extract
type ExtractName<T> = {
  [Property in keyof T as Extract<Property, 'name' | 'age'>]: T[Property]
}
type SomeNameExtracted = ExtractName<{ id: string, name: string; age: number }> // { name: string, age: number }

Кроме того, мы можем сопоставлять типы, используя Условные типы, например тернарный оператор.

type StringIdentifiable<Type> = {
  [Property in keyof Type]: Type[Property] extends { id: string } ? true : false;
};

type SomeStringIdentifiable = StringIdentifiable<{
  user: { id: number, name: string, age: number },
  order: { id: string, amount: number }
}> // { user: false; order: true; }

Случаи использования

1. Давайте создадим состояние редуктора, в котором сущности хранятся в нормализованном виде.

// Define entities
type Entities = {
  essay: Essay // {id: string, title: string}
  user: User // {id: string, name: string}
}

// Instead of writing this:
type State = {
  essay: Record<Essay['id'], Essay>
  user: Record<User['id'], User>
}

// We can define `State` type by mapping `Entities`:
// Just add a new entity to `Entities` and it will be added to `State` automatically.
type State = {
  [Property in keyof Entities]: Record<Entities[Property]['id'], Entities[Property]>
}

2. Чтобы использовать неизменяемый дизайн, Readonly‹T› полезен для определения сущностей. Однако в тех случаях, когда черновой объект необходим для изменения, можно использовать оператор -readonly для отображения доступного для записи объекта.

type Writable<T> = {
  -readonly [Property in keyof T]: T[Property]
}
type User = Readonly<{ id: string; name: string; }>
type WritableUser = Writable<User> // { id: string; name: string; }

Смысл

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

Однако иногда определение типов может потребовать много повторяющейся работы. Сопоставленные типы — очень полезная функция для уменьшения повторяемости и сохранения нашего кода СУХИМ. Это также помогает иметь меньше источников истины для наших определений типов.