Автор Ароль Виньолас, руководитель отдела исследований (или технический директор) @ Codeworks

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

Например, при следовании этой стратегии у вас может получиться что-то вроде этого:

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

  • auth_token будет использоваться в основном централизованно: ApiClient сервис или промежуточное ПО.
  • movies — это список сущностей. Он будет использоваться во всем приложении для отображения информации, предоставляемой приложением.
  • fetching — это презентационный флаг, мы собираемся использовать его в конкретном представлении (компоненте), чтобы показать, например, счетчик загрузки.

Мы можем видеть, как свойства смешиваются с точки зрения значения, потому что movies (данные) не имеют того же уровня важности, что и fetching (флаг) - и >использование- потому что они используются в совершенно разных местах и ​​в разное время.

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

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

Это состояние создается с помощью combineReducers для определения отдельных частей состояния, которыми будут управлять разные редюсеры. Обратите внимание, что я использовал Сокращенные имена свойств для определения свойств объекта: authentication — это сокращение для authentication: authentication , это вторая функция-редуктор, которую я опишу позже.

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

Редуктор этого будет прослушивать определенные типы действий, связанные с аутентификацией: SIGN_IN_SUCCESS SIGN_UP_SUCCESS LOG_OUT

Это самая особенная часть редуктора. Мы привыкли видеть редукторы, основанные на огромных switch над action.type со списком преобразований для состояния. Но этот будет управлять всем набором данных нашего Front-End с менее чем 20 строками кода.

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

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

Если все ваши данные локальные, у вас не возникнет проблем с управлением ими таким образом, но если вы используете API, скорее всего, он будет предоставлять данные во вложенном виде. Для обработки данных в наш формат можно использовать normalizr. После некоторой настройки он сможет сгладить вызов функции normalize . Он вернет объект с двумя свойствами: entities и result. Сущностями будут данные, извлеченные и нормализованные в нашем формате; Результатом будет соотношение идентификаторов корневого объекта, который вы только что извлекли.

Сущности! Какое совпадение, а? Не совсем. На самом деле я получил этот шаблон из Примера реального мира Redux, который использует API Github, нормализуя свои данные после него.

Благодаря этому формату мы можем написать редюсер, который всего несколькими строками управляет всей entities частью состояния.

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

Предупреждение: этот редьюсер только добавляет или перезаписывает данные по мере их поступления, нет необходимости удалять данные из нашего состояния. Некоторые могут сказать, что в этом нет необходимости, поскольку удаление можно рассматривать как изменение флага delete элемента (мягкое удаление). Что вы думаете? Любые предложения по улучшению этого случая? Давайте обсудим это в комментариях!

Все наши данные обрабатываются автоматически и объединяются в состояние. Однако мы должны быть осторожны: наши данные хранятся в объектах, массивов нет, поэтому нет порядка.

Мы должны сохранить порядок результатов, предоставляемых Back-End, они могут тратить много ресурсов (то есть денег 💸), чтобы определить порядок, в котором фильмы представлены каждому пользователю (Netflix, кто-то?). Но нормализуя их, мы игнорируем этот порядок. Вот почему normalizr также дает нам results отношение идентификаторов, представляющих извлеченные ресурсы.

Вы получили один фильм с идентификатором 101 (API отвечает объектом)? Свойство results будет 101 ; получение массива фильмов с идентификаторами 101 и 100 (API отправляет массив)? results будет этот массив [101,100] (с сохранением порядка). См. пример ниже:

Обратите внимание, что исходный ответ был массивом фильмов, и свойство results нормализованного объекта также является массивом, сохраняя исходный порядок.

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

Как видите, при сопоставлении состояния с реквизитами в наших контейнерах мы создаем массив, сопоставляющий все элементы в результате (state.pages.boxOffice.myListMovies, список идентификаторов) с фактическими данными в entities.

Идея состоит в том, чтобы снова разделить свойство state.pages на набор всех страниц, которые вы показываете в своем приложении. Например, каждый компонент, загруженный маршрутом React Router.

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

В будущем я хотел бы написать о Redux’s Reselect, используя этот паттерн.

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

Удачного кодирования!

Первоначально опубликовано на medium.com 13 августа 2018 г.