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

Не было библиотеки эффектов Redux, которая отвечала бы моим потребностям, поэтому в итоге я создал что-то новое. Я поделюсь идеями, которые позаимствовал из других библиотек эффектов, и как они используются в redux-funk.

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

Redux Loop - декларативные эффекты в редукторах

Пример ниже демонстрирует некоторые из замечательных идей Redux Loop. Это код редуктора, который возвращает инструкцию на увеличение счетчика после задержки:

Вызов функции loop выше можно прочитать как «вернуть состояние без изменений и вызвать incrementAsync с аргументами [action.delay]».

Преимущество размещения асинхронных материалов в редукторах заключается в том, что теперь есть только одно место, куда можно заглянуть при попытке выяснить «Что делает приложение при отправке этого действия?». То, должно ли действие обрабатываться синхронно или асинхронно, становится деталью реализации, а не решением, влияющим на то, в какой части приложения находится ваш код. Для сравнения, с Redux Thunk асинхронный материал живет в создателях действий, а с Redux Saga - асинхронный материал. в сагах.

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

Я не использовал Redux Loop, потому что мне нужно было создавать настраиваемую композицию редуктора, например, делегировать другому редуктору из редуктора. Это сложно с Redux Loop, потому что его эффекты должны передаваться вверх по дереву состояний с использованием isLoop, getEffect, getModel и пакет .

Проблема в том, что в композиции редукторов мы хотим, чтобы дочерние редукторы имели доступ только к части дерева состояний, а не ко всему. Таким образом, при сохранении эффектов в состоянии (например, в Redux Loop) эффект каким-то образом должен добраться до вершины дерева, чтобы промежуточное программное обеспечение могло его видеть. Альтернативой является полный обход дерева в поисках эффектов, что, вероятно, не является хорошей идеей. Я позаимствовал решение у Redux Side Effect и опишу его в следующем разделе.

Вот версия redux-funk редуктора асинхронного счетчика:

В приведенном выше коде вызов используется для постановки эффекта в очередь. Эффект [incrementAsync, [action.delay]] можно прочитать как «вызов incrementAsync с action.delay в качестве первого аргумента». Я использую funk для ссылки на эффекты формы [func, [arguments…]]. redux-funk добавляет массив funks наверху дерева состояний для постановки асинхронных эффектов в очередь. Итак, чтобы протестировать редуктор, использующий redux-funk, вы можете просто проверить ключ funks:

Побочный эффект Redux - Использование объекта действия

Код Redux Loop должен иногда перепрыгивать через обручи, чтобы передать эффекты вверх по дереву состояний. Redux Side Effect позволяет избежать этих трудностей и имеет крошечное количество строк ​​из-за уловки - он использует объект action для передачи эффектов.

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

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

Несмотря на элегантность этого решения, я не стал использовать Redux Side Effect, потому что:

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

redux-funk также использует уловку сохранения действий в объекте action, чтобы избежать передачи эффектов. Но реализация сохраняет чистоту редукторов. call (action, funk) добавляет фанк к действию, используя символ в качестве ключа:

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

Вот реализация coalesceEffects:

Из React-Redux - подписка на магазин

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

Поскольку redux-funk создает эффекты как часть состояния хранилища, вы можете просто подписаться на хранилище, а затем программировать эффекты. Я сделал небольшой помощник, runFunks, для обработки эффектов аналогично тому, как это делает Redux Loop:

runFunks позволяет фактически вызывать эффекты, описанные в редукторах. В примере счетчика функция возвращает обещание для действия. runFunks вызывает функцию и выполняет обещанное действие INCREMENT. Когда обещание выполняется, runFunks отправляет действие:

Вот реализация runFunks:

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

Попробуй

Если вы зашли так далеко, то вы видели весь исходный код redux-funk!

Надеюсь, у вас будет шанс попробовать и поиграть с примерами, которые адаптированы из примеров Redux Saga.

Хакерский полдень - это то, с чего хакеры начинают свои дни. Мы часть семьи @AMI. Сейчас мы принимаем заявки и рады обсуждать рекламные и спонсорские возможности.

Если вам понравился этот рассказ, мы рекомендуем прочитать наши Последние технические истории и Современные технические истории. До следующего раза не воспринимайте реалии мира как должное!