Это первая статья из серии, в которой мы попытаемся поставить точку в понимании того, из чего состоит Redux, и как мы можем использовать эти знания для повышения производительности и качества кода.

  1. Углубляемся в Redux: типы действий
  2. Углубляемся в Redux: типы редукторов

Лично я не люблю длинные предисловия, но это не займет много времени. Я на 100% уверен, что вы сталкивались с этой библиотекой, вероятно, не раз и, скорее всего, уже используете ее в своем проекте.

Все, что нам нужно знать об основных принципах Redux, это всего несколько строк из официальной документации:

Все состояние вашего приложения хранится в дереве объектов внутри единого store.
Единственный способ изменить дерево состояний - создать действие, объект, описывающий, что произошло.
Чтобы указать, как действия преобразуют дерево состояний, вы пишете чистые редукторы .

В последнее время я часто встречал мнение, что Redux - это библиотека, невероятно сложная для понимания, с высокой кривой обучения и есть варианты, более терпимые для новичков.

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

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

Меня зовут Серж, я Front-End разработчик на сайте weekend.com. В нашем проекте мы активно используем React с Redux с их богатой экосистемой.

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

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

Когда я начал работать с Typescript и Redux, было трудно понять, как мне правильно использовать все эти типы. И не могу сказать, что я встречал десятки статей в сети. Я надеюсь, что это руководство послужит вам хорошей отправной точкой для начала вашего путешествия в мир строгой типизации 😜

Действие

Начнем с самого простого и очевидного - Action. Понимание этого строительного компонента даст нам хорошую основу для дальнейших открытий. Для самостоятельного чтения вы можете перейти по этой ссылке.

Данный интерфейс является первым в нашем списке:

export interface Action<T = any> {
  type: T
}

Как вы могли заметить, это не имеет большого значения. И это то, чего мы пытались добиться. Интерфейс описывает, как будет выглядеть ваше действие. Переменная типа T по умолчанию имеет тип any, но для обеспечения безопасности типов я рекомендую поместить туда значение. По дизайну он должен соответствовать type полю вашего следующего действия. Следует отметить, что поле type является обязательным, иначе вы получите сообщение об ошибке.

Обратите внимание, что мы говорим о Action, а не о ActionCreator. Action - это просто объект, цель которого - описать ваше действительное намерение с помощью поля type и, возможно, путем добавления некоторых дополнительных данных. И не фабрика функций, которой по сути является ActionCreator .

Здесь вы можете увидеть обычную акцию с поддержкой TS:

const DELETE_TODO = 'DELETE_TODO';
const deleteTodo: Action<typeof DELETE_TODO> = {
  type: DELETE_TODO,
}

Из-за того, что в реальном мире мы редко используем Action в качестве объекта, я могу предположить, что этот интерфейс будет не так удобен для вас. Но дальше мы увидим хороший пример его использования вместе сActionCreator. Кстати, вот пример того, что вы можете получить после попытки добавить еще одно поле, которое не было описано в Action интерфейсе:

Есть еще один тип Действия, который вы можете встретить в Redux - AnyAction. Но в отличие от предыдущего, он не указывает ничего конкретного, а расширяет известные Action. Этот дополнительный шаг гарантирует существование поля type и в то же время позволяет нам добавлять любое дополнительное свойство в качестве полезной нагрузки.

export interface AnyAction extends Action { 
  [extraProps: string]: any
}

ActionCreator

Сначала небольшое резюме: ActionCreator - это простая функция, которая возвращает Action. Думаю, вы слышали это не раз. Но теперь давайте посмотрим, что такое ActionCreator с точки зрения типов:

export interface ActionCreator<A> {  
  (...args: any[]): A
}

Что мы здесь видим? ActionCreator сам по себе является универсальным типом, который получил только один аргумент типа - <A>, который на самом деле является типом действия, определенным выше. Эта часть должна быть уже знакома (...args: any[]), и это означает, что функция может получать неограниченное количество аргументов с любым типом. Пример использования:

ActionCreatorsMapObject

Последний интерфейс на сегодня - это ActionCreatorsMapObject:

export interface ActionCreatorsMapObject<A = any> {
  [key: string]: ActionCreator<A>
}

На этом этапе не должно быть ничего сложного.

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

Он также является общим и принимает только один параметр типа - тип действия. Но как это может помочь нам в случае нанесенного на карту объекта? - спросите вы. Хорошо, хороший вопрос, и у Typescript есть хороший ответ, представленный Union Types.

В этом примере будут показаны типы объединения в действии:

Вывод

Я надеюсь, что это небольшое предисловие упростит использование Redux с Typescript или, может быть, эта статья побудит вас начать использовать Typescript в вашем проекте.

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