При использовании 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 элемента (мягкое удаление). Что вы думаете? Есть предложения по улучшению этого дела? Давайте обсудим это в комментариях!

Редуктор страниц

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

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

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

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

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

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

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

Подведение итогов

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

В будущем я хотел бы написать о Redux Reselect с использованием этого шаблона.

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

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