Данные, которые потребляет любая часть нашего приложения, не будут слишком отличаться от исходных данных, которые приложение получает для работы.

Данные относительны, где бы они ни существовали

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

// types.ts
type User = {
  name: string, // "John Doe"
  age: number, // 22
  score: number, // 6.3
}

Теперь где-то в нашем приложении есть компонент UserScore, который имеет следующие свойства.

// UserScore.tsx
type UserScore = {
  name: string,
  age: number,
  score: number,
  goals: number,
}

Откуда взялись данные о голах? Вероятно, это могло быть из другого ответа API. Но пока мы просто решили создать тип опоры этого компонента, используя примитивные типы.

Все шло хорошо, пока однажды компания не решила изменить свойство оценки. Они не хотели хранить точные числа, кроме одного из них.

score: 'S' | 'A+' | 'A' | 'B' | 'C' | 'D' | 'U'

Итак, мы изменим тип пользователя, как показано ниже. Почему тип пользователя? Потому что именно здесь данные поступают в наше приложение, и, естественно, именно здесь мы обновим их в первую очередь.

type User = {
  name: string, // "John Doe"
  age: number, // 22
  score: Grade, // B
}
type Grade = 'S' | 'A+' | 'A' | 'B' | 'C' | 'D' | 'U'

Мы вводим новый тип под названием Grade и подключаем его к нашему типу User.

Теперь, поскольку мы уже используем машинописный текст в проекте, мы получим ошибку при попытке передать оценку из типа User в компонент UserScore.

Если мы не будем осторожны, чтобы исправить ошибку, мы можем написать адаптер для подключения UserScore к типу User, но с этого момента все станет еще хуже. .

Пользовательский интерфейс / потребители могут подключаться к потоку и преобразовывать типы во что угодно. Но поток сохраняет типы во всем приложении.

Выбрать, расширить и объединить

Мы можем обновить UserScore, как показано ниже.

type User = {
  name: string, // "John Doe"
  age: number, // 22
  score: Grade, // B
}
type Grade = 'S' | 'A+' | 'A' | 'B' | 'C' | 'D' | 'U'
type Sport = {
  name: string,
  goals: number
}
type UserScore = 
  Pick<User, 'name' | 'age' | 'score'> 
  & 
  Pick<Sport, 'goals'>

Pick позволяет нам выбирать набор свойств из типа. Таким образом, UserScore сохраняет свои отношения с Пользователем и Спортом.

const userScore: UserScore = {
  name: 'Karthick',
  age: 33,
  score: 'A+',
  goals: 32,
}

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

Это дисциплинированное манипулирование типами обеспечит отличное взаимодействие с разработчиками. В любой момент в потоке данных мы знаем, к какому типу относится оценка.

Когда происходят изменения, этой настройки достаточно изменить тип источника, и каждый производный тип, который привязан к типу источника, будет обновлен. Теперь задача пользовательского интерфейса / потребителя - адаптироваться.

Давайте разработаем наши типы для нашего эпического проекта клонирования.

Типы источников

В нашей базе данных хранятся типы Игра, Издатель и Пользовательская игра. И мы начинаем со схемы этих ресурсов во внешнем коде.

// Clone start branch:
git clone -b 03-create-types-start github.com/karthickthankyou/epic-clone
// Clone final branch:
git clone -b 03-create-types-final github.com/karthickthankyou/epic-clone

Мы понимаем, что теги, функции, примечания, платформа и статус имеют набор предопределенных значений на выбор. Мы сохраняем их как типы объединения, как показано ниже.

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

Когда у нас все в порядке с исходными типами, мы должны сосредоточиться на создании производных типов.

Производные типы

Взгляните на образец предмета в типе игры. Это очень сложно

На нашей домашней странице мы перечисляем около 40–60 игр. В нижеследующем разделе карточек показан общий сценарий, в котором нам нужно лишь несколько свойств из Игры.

Выбирать

Следовательно, мы создаем тип GameLight. Мы можем использовать Частично. Но он слишком широк, и при этом мы теряем всю обязательность. Выбрать позволяет нам сохранить обязательные и дополнительные функции в качестве исходного типа.

Pick<Type, Keys>

Мы выбираем необходимые функции, необходимые для отображения игр, кроме подробной страницы продукта. Тип выглядит так, как показано ниже.

Можем ли мы выбрать один из вариантов в типе объединения?

У нас есть сценарий в нашем проекте, где нам нужно было использовать только часть типа объединения GameGenre. У него много возможных значений, но на главном экране мы выбрали разделы только для «Действие» | «Приключение» | «Головоломка» | «Повествование».

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

// Create a subset of union types
export type PickUnion<T, U extends T> = U

Вышеупомянутый служебный тип имеет два общих типа, один из которых является родительским. Второй - подмножество родителя. Мы используем указанный выше тип утилиты, как показано ниже.

type selectedGenre = PickUnion<GameGenre, 'Action' | 'Adventure' | 'Puzzle' | 'Narration'>
// Translates to type selectedGenre = "Action" | "Adventure" | "Puzzle" | "Narration"

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

Будет много компонентов, которым требуются разные свойства, но, используя описанные выше методы, мы можем выполнить именно то, что требуется компоненту!

Увидимся в следующем разделе.

Больше контента на plainenglish.io