Как стать автором
Обновить

TypeScript в React-приложениях. 5. Связанная типизация

Время на прочтение3 мин
Количество просмотров2.4K

Данные в React-приложениях передаются по однонаправленному потоку: через пропсы (или React Context) от родительских компонентов к дочерним и через колбэки от дочерних к родительским. Типизация пронизывает этот поток, помогая разработчику документировать данные на разных слоях, обнаруживать ошибки на стадии написания кода и проектировать упрощённую логику.

При изменении логики на одном уровне приложения возможно появление ошибок в зависимом коде, которые анализатор TypeScript подсвечивает. Разработчику остаётся переписать типизацию и логику. Чтобы каждый раз не обслуживать зависимые типы, их можно конструировать на основе базовых, таким образом связывая типы.

Содержание

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

Рассмотрим обобщённый вид потока данных и их трансформацию в приложении React, использующее в качестве state-менеджера Redux.

Изображение 5.1 Поток и трансформация данных в React приложении
Изображение 5.1 Поток и трансформация данных в React приложении

Каждый слой в приложении выполняет свою задачу. Точки трансформации данных могут располагаться в других местах или вовсе отсутствовать. Это зависит от целесообразности и договорённостей на проекте.

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

Авторское отступление

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

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

Мы можем увидеть, что данные, пришедшие на фронт и отправляемые на бэк, каким-то образом связаны. Для того, чтобы TypeScript контролировал эту связь, нужно связывать типизацию разных слоёв.

К примеру, данные с бэкэнда очень похожи на то, что мы хотим сохранить в хранилище. Тогда нужно создать тип, описывающий данные в store, на основе типа, описывающего модель данных с бэка:

type BackendData = {
  profile_id: string | number;
  // другие поля модели
};

type Id = BackendData['profile_id'];

type BackendDataWithoutId = Omit<BackendData, 'profile_id'>;

type StoreData = BackendDataWithoutId & {
  id: Id
}

Код 5.1. Привязывание типа StoreData к типу BackendData с преобразованием поля profile_id

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

type StoreData = Omit<BackendData, 'profile_id'> & {
  id: BackendData['profile_id']
}

Если в процессе создания новых фич в приложении, разработчики выясняют, что поле profile_id может иметь значение null, то достаточно будет это прописать в типе BackendData, связанные с ним типы автоматически подхватят новый тип и анализатор подсветит проблемы в логике на нижних слоях.

Точно так же мы могли бы описывать пропсы компонента, привязывая их типы к типу хранилища:

type Props = Pick<StoreData, 'id'> & {
  // другие пропсы
}

Код 5.2. Привязывание типа Props к типам отдельных полей StoreData

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

Таким образом через типы связываются все данные в приложении (если они связаны логикой).

В TypeScript многие операторы и конструкции используют базовый тип для построения нового:

  • keyof получает ключи из базового типа, удобен, когда нужно ориентироваться на ключи какого-то типа, вместо дублирования строк;

  • typeof определяет тип константы, как её видит анализатор, удобен вместо явного прописывания типа существующим в коде константным значениям;

  • mapped типы (с использованием оператора in) позволяет создавать объектные типы, используя ключи и их типы базового.

  • Pick, Omit, Partial и другие основаны на mapped типах и удобны для изменения исходного объектного типа.

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

Следующая статья: TypeScript в React-приложениях. 6. Изящная типизация

Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Публикации

Истории

Работа

Ближайшие события