Когда я пойму, что готов к Redux?

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

Автор: Саймон Шварц

Эта статья о том, когда Вам стоит задуматься об использовании Redux в проекте, о проблемах, которые он решает и пользе, которую мы можем извлечь. Она основа на том, чему мы научились увеличивая масштабы нашего React-приложения.

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

Что такое состояние приложения?

Состояние — это всего лишь данные. Можете представлять это как пачку данных, таких как: имя пользователя, залогинен ли пользователь, грузится ли в данный момент страница, и так далее. Состояние приложение может меняться в зависимости от таких вещей как действия пользователя, или же ответ от API. Приложение будет считывать состояние для того, чтобы определить, как именно сейчас должен отображаться пользовательский интерфейс (User Interface, UI).

Что такое Redux?

Redux — это инструмент для управления состоянием приложения. Очень распространён в React-приложениях.

Почему мы решили использовать Redux

Преждевременная оптимизация — корень всех зол. — Тони Хоар

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

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

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

Теперь же позвольте рассказать о моём опыте увеличения масштаба нашего приложения.

День 1

Мы начали использовать локальное состояние React-компонентов.

React реализует концепцию однонаправленного потока данных. Это означает, что компоненты отправляют состояние вниз (или книзу) в виде свойств (props).

Мы расположили наше состояние на верхнем уровне (в корне), теперь отправляем данные вниз в виде свойства. Легко.

Поток изменения состояния

День 5

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

Никаких проблем, мы решаем эту проблему с помощью “подъёма состояния”. Это означает, что мы поднимаем состояние (и функции, которые изменяют это состояние) к ближайшему предку (компоненту-контейнеру). Мы привязываем функции к компоненту-контейнеру и передаём их к низу в виде свойств. Это означает, что компонент-потомок может вызывать изменения в состоянии в компоненте-родителе, это обновит все другие компоненты в дереве. Отлично.

1: Инициализируется изменение состояния; 2: Поток изменения состояния

День 20

Мы добавили несколько новых возможностей и поток данных состояния нашего приложения выглядит примерно так…

1: Инициализируется изменение состояния; 2: Поток изменения состояния

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

  • Наше приложение основывается на пользовательском интерфейсе. Состояние нашего приложения не обязательно следует интерфейсу.
  • Форма состояния нашего приложения распалась на отдельные части и разнеслась среди компонентов.
  • Функции, которые изменяют состояние нашего приложения разнеслись среди компонентов.
  • Для того, чтобы посмотреть цельное состояние приложения, необходима “ментальная” модель структуры приложения.
  • Мы передаём одни и те же свойства через несколько уровней компонентов.
  • Становится сложнее отслеживать изменения состояния и дебажить приложение.

Если Вы сталкиваетесь с некоторыми из обозначенных выше ошибок, это означает, что Вы готовы к Redux.

Как же работает Redux?

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

Наиболее распространённой жалобой на Redux является то, что он слишком подробный и поэтому требует времени для того, чтобы начать в нём ориентироваться. Он вынуждает разработчиков явно описывать как состояние приложения обновляется через действия (actions) и редьюсеры (reducers). Это позволяет поддерживать состояние приложения как единое глобальное хранилище, а не работать с его отдельными частями в локальном состоянии компонентов.

Ниже — беглый обзор того, как структурирован Redux.

Действия

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

Редьюсер

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

Хранилище

Хранилище — это состояние приложения, сохранённое в виде объектов. React-компоненты могут быть подписаны на хранилище и в любой момент обновления хранилища обновятся и подписанные компоненты.

Вот как это выглядит. Я обновил версию диаграммы, но на этот раз с использованием Redux.

Запомните: в Redux мы отправляем действие, которое активирует редьюсер, который обновляет наше хранилище.

1: Инициализируется изменение состояния; 2: Поток изменения состояния

Чему мы научились

После того, как мы стали использовать Redux, нами были замечены следующие преимущества:

Упрощение наших React-компонентов

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

Разделение проблем

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

Качество

  • Стало гораздо легче дебажить стейт, особенно с помощью Redux logger (Привет, Женя)
  • Redux оказался невероятно простым, для написания для него unit-тестов.

Подытоживая

Преждевременная оптимизация — корень всех зол. — Тони Хоар

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

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

  • Вид пользовательского интерфейса варьируется в зависимости от состояния приложения.
  • Поток данных состояния не всегда протекает однононаправленно.
  • Обычные действия пользователей в Вашем приложении инициируют множественное обновление состояния.
  • Много несвязанных компонентов обновляют состояние таким же образом.
  • Дерево состояний представляет собой сложную структуру.
  • Состояние обновляется многими различными путями.
  • Вам необходимо отменить последние сделанные пользователем действия.

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

  • Мы не хотим, чтобы состояние формы оставалось после того, как компонент был убран из DOM.
  • Состояние валидации форм не используется нигде вне этого компонента.

Хотите начать?

Вот несколько фантастических ресурсов, которые я использовал для изучения Redux.

Спасибо за прочтение!