Pull to refresh

Сложности работы с Redux и их решение

Reading time4 min
Views5K
image

Redux среди нас


Это один из самых популярных state-manager`ов.

Он прост в использовании, прозрачен и предсказуем. С его помощью можно организовать хранение и изменение данных. А если считать, что action`ы и reducer`ы являются частью redux`а, то можно без преувеличения утверждать, что он является держателем всей логики предметной области (aka бизнес-логики) наших приложений.

А все ли так радужно?


При всей своей простоте и прозрачности, использование redux`а обладает рядом недостатков…

Данные в stateredux`а лежат в простом javascript-объекте и могут быть получены привычным образом, нужно лишь знать путь.

Но как мы организуем эти данные? Есть всего 2 варианта: плоский список и иерархическая модель.

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

Пример
const dataHierarchy = {
  user: {
    id,
    name,
    experience,
    achievements: {
      firstTraining,
      threeTrainingsInRow,
    },
  },
  training: {
    currentSetId,
    status,
    totalAttemptsCount,
    attemptsLeft,
    mechanics: {
      ...
    },
  },
};

Тут под ключом user хранятся данные пользователя, в частности achivements. Но достижениям не нужно ничего знать про остальные данные пользователя.

Точно так же, конкретной механике тренировки не нужно знать, сколько попыток осталось у пользователя — это данные training в целом.


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

image

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

Еще одной сложностью является тестирование. Да, в документации к redux тестированию посвящена отдельная статья, но сейчас речь идет не о тестировании отдельных артефактов вроде reducer`ов и action-creater`ов.

Данные, action`ы и reducer`ы как правило взаимосвязаны. А одно дерево логически связанных данных часто обслуживается несколькими reducer`ами, тестировать которые нужно в том числе и вместе.

Добавим к этому списку selector`ы, результаты которых зависят в частности от работы reducer`ов…

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

А что делать, если мы придумали структуру, к примеру, с данными пользователя, включающими списки друзей, любимых песен и чего-нибудь еще, а так же функционал их изменения через action`ы-reducer`ы. Возможно, мы даже написали пачку тестов на весь этот функционал. А теперь в соседнем проекте нам нужно то же самое…

Как дешево пошарить код?

image

Поиск решения


Для того, чтобы придумать как сохранить плюсы redux`а и избавиться от описанных недостатков, надо понять, что от чего зависит в жизненном цикле данных:

  • Action`ы сообщают о произошедших событиях, пользовательских и не только
  • Reducer`ы реагируют на action`ы и изменяют или не изменяют состояние данных
  • Изменение данных — само по себе событие и может быть причиной изменения других данных

image

Controller в данном случае является абстракцией, обрабатывающей как действия пользователя, так и изменения данных в store`е. Ему вовсе не обязательно быть отдельным классом, как правило, он размазан по компонентам.

Объединяем весь redux-зоопарк в черный ящик


А что если завернуть action`ы, reducer`ы и selector`ы в один модуль и научить его не зависеть от конкретного пути для расположения своих данных?

Что если все действия user`а, для примера, будут совершаться посредством вызова метода экземпляра: user.addFriend(friendId)? А данные получаться через getter: user.getFriendsCount()?

Что если мы сможем импортировать весь функционал пользователя простым import`ом?

const userModule from ‘node_modules/user-module’;

Удобно? Особенно учитывая, что для этого не надо писать кучу лишнего кода:
npm пакет redux-module-creator предоставляет весь функционал для создания слабосвязных, переиспользуемых и тестируемых redux-модулей.

Каждый модуль состоит из controller`а, reducer`а и action`ов и имеет следующие особенности:

  • интегрируется в store через вызов метода-интегратора, при этом для изменения места интеграции надо поменять только место вызова интегратора и его параметр

    image
  • контроллер имеет связь со своей частью данных в store`е, запоминая путь, который единожды передается в integrator(). Это исключает необходимость знать его при использовании данных

    image
  • контроллер является держателем всех необходимых селекторов, адаптеров и т.д.
  • для отслеживания изменений есть возможность подписываться на изменения именно в “своих” данных
  • reducer`ы могут использовать контекст вызова — экземпляр модуля и получать из него actionType`ы, принадлежащие модулю. Это исключает необходимость импортировать кучу action`ов и снижает вероятность ошибки
  • аction`ы получают контекст использования, поскольку становятся частью экземпляра модуля: теперь это не просто trainingFinished, а readingModule.actions.trainingFinished
  • action`ы теперь существуют в пространстве имен модуля, что позволяет создавать для разных модулей события с одинаковыми именами
  • каждый модуль может быть инстанциирован несколько раз, а каждый экземпляр — интегрирован в разные части store
  • action`ы для разных экземпляров модуля имеют разные actionType — можно реагировать на события конкретного экземпляра

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

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

А поскольку это все тот же redux и все те же reducer`ами, из них можно строить любую структуру, которую требует логика предметной области вашего приложения.
Tags:
Hubs:
Total votes 10: ↑10 and ↓0+10
Comments14

Articles