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