Redux thunk что это
Как делать асинхронные Redux экшены используя Redux-Thunk
Приветствую Хабр! Представляю вашему вниманию перевод статьи — Asynchronous Redux Actions Using Redux Thunk, автора — Alligator.io
По умолчанию, экшены в Redux являются синхронными, что, является проблемой для приложения, которому нужно взаимодействовать с серверным API, или выполнять другие асинхронные действия. К счастью Redux предоставляет нам такую штуку как middleware, которая стоит между диспатчом экшена и редюсером. Существует две самые популярные middleware библиотеки для асинхронных экшенов в Redux, это — Redux Thunk и Redux Saga. В этом посте мы будем рассматривать первую.
Redux Thunk это middleware библиотека, которая позволяет вам вызвать action creator, возвращая при этом функцию вместо объекта. Функция принимает метод dispatch как аргумент, чтобы после того, как асинхронная операция завершится, использовать его для диспатчинга обычного синхронного экшена, внутри тела функции.
Если вам интересно, то Thunk, это концепт в мире программирования, когда функция используется для задержки выполнения операции.
Установка и настройка
Во первых, добавьте redux-thunk пакет в ваш проект:
Затем, добавьте middleware, когда будете создавать store вашего приложения, с помощью applyMiddleware, предоставляемый Redux’ом:
Основное использование
Обычно Redux-Thunk используют для асинхронных запросов к внешней API, для получения или сохранения данных. Redux-Thunk позволяет легко диспатчить экшены которые следуют «жизненному циклу» запроса к внешней API.
Например, у нас есть обычное todo приложение. Когда мы нажимаем «добавить todo», обычно, сперва диспатчится экшен, который сообщает о старте добавления нового todo. Затем, если todo элемент успешно создан и возвращен сервером, диспатчится другой экшен, с нашим новым todo элементом, и операция завершается успешно. В случае, если сервер по каким то причинам возвращает ошибку, то вместо добавления нового todo диспатчится экшен с ошибкой, что операция не была завершена.
Давайте посмотрим, как это может быть реализовано с помощью Redux-Thunk. В компоненте, экшен диспатчится как обычно:
В самом экшене дело обстоит намного интереснее. Здесь мы будем использовать библиотеку Axios, для ajax запросов. Если она у вас не установлена, то добавьте ее так:
Обратите внимание, как наш addTodo action creator возвращает функцию, вместо обычного экшен объекта. Эта функция принимает аргумент dispatch из store.
Внутри тела функции мы сперва диспатчим обычный синхронный экшен, который сообщает, что мы начали добавление нового todo с помощью внешней API. Простыми словами — запрос был отправлен на сервер. Затем, мы собственно делаем POST запрос на сервер использую Axios. В случае утвердительного ответа от сервера, мы диспатчим синхронный экшен, используя данные, полученные из сервера. Но в случае ошибки от сервера мы диспатчим другой синхронный экшен с сообщением ошибки.
Когда мы используем API, который действительно является внешним (удаленным), как JSONPlaceholder в нашем случае, легко заметить что происходит задержка, пока ответ от сервера не приходит. Но если вы работаете с локальным сервером, ответ может приходить слишком быстро, так что вы не заметите задержки. Так-что для своего удобства, вы можете добавить искусственную задержку при разработке:
actions/index.js (кусок кода)
А для тестирования сценария с ошибкой, вы можете напрямую выбросить ошибку:
actions/index.js (кусок кода)
Для полноты картины, вот пример, как наш todo редюсер может выглядеть, что-бы обрабатывать полный «жизненный цикл» запроса:
getState
Функция, возвращаемая асинхронным action creator’ом с помощью Redux-Thunk, также принимает getState метод как второй аргумент, что позволяет получать стейт прямо внутри action creator’а:
actions/index.js (кусок кода)
При выполнении этого кода, текущий стейт просто будет выведен в консоль. Например:
Использование getState может быть действительно полезным, когда надо реагировать по разному, в зависимости от текущего стейта. Например, если мы ограничили максимальное количество todo элементов до 4, мы можем просто выйти из функции, если этот лимит превышается:
actions/index.js (кусок кода)
Забавный факт — а вы знали что код Redux-Thunk состоит только из 14 строк? Можете проверить сами, как Redux-Thunk middleware работает под капотом
Разбираемся в redux-saga: От генераторов действий к сагам
Любой redux разработчик расскажет вам, что одной из самых тяжелейших частей разработки приложений являются асинхронные вызовы — как вы будете обрабатывать реквесты, таймауты и другие коллбэки без усложнения redux действий(actions) и редьюсеров(reducers).
В этой статье я опишу несколько различных подходов к управлению асинхронностью в вашем приложении, начиная от простых подходов как redux-thunk, заканчивая более продвинутыми библиотеками вроде redux-saga.
Мы собираемся использовать React и Redux, поэтому будем полагать, что вы имеете хотя бы какое то представление о том как они работают.
Генераторы действий (Action creators)
Взаимодействие с API довольно частое требование в приложениях. Представьте, что нам необходимо показывать случайную картинку собаки, когда мы нажимаем на кнопку.
мы можем использовать Dog CEO API и что-то довольно простое вроде вызова fetch внутри генератора действия (action creator).
Нет ничего плохого в таком подходе. При прочих равных всегда лучше использовать более простой подход.
Однако, использование только Redux не дает нам достаточно гибкости. Ядро Redux это контейнер состояния (state container), который поддерживает только синхронные потоки данных.
На каждое действие, в хранилище (store) посылается объект, описывающий что произошло, затем вызывается редюсер (reducer) и состояние (state) сразу обновляется.
Но в случае асинхронного вызова, вам необходимо сначала дождаться ответа и затем уже, если не было ошибок, обновить состояние. А что если у вашего приложения есть некая сложная логика/workflow?
Для этого Redux использует промежуточные слои (middlewares). Промежуточный слой это кусок кода, который выполняется после отправки действия, но перед вызовом редюсера.
Промежуточные слои могут соединяться в цепочку вызовов для различной обработки действия (action), но на выходе обязательно должен быть простой объект (действие)
Для асинхронных операций, Redux предлагает использовать redux-thunk промежуточный слой.
Redux-thunk
Redux-thunk является стандартным путем выполнения асинхронных операций в Redux.
Для нашей цели, redux-thunk вводит понятие преобразователь(thunk), что является функцией, которая предоставляет отложенное выполнение, по необходимости.
Значение 3 сразу присваивается переменной x.
Однако, если у нас есть выражение наподобие
То суммирование выполняется не сразу, а только при вызове функции foo(). Это делает функцию foo преобразователем(thunk).
Redux-thunk позволяет генератору действия (action creator) отправлять функцию в дополнении к объекту, конвертируя таким образом генератор действия в преобразователь.
Ниже, мы перепишем предыдущий пример используя redux-thunk
На первый взгляд он не сильно отличается от предыдущей версии.
Преимуществом использования redux-thunk является то, что компонент не знает, что выполняется асинхронное действие.
Т.к. промежуточный слой автоматически передает функцию dispatch в функцию, которую возвращает генератор действий, то снаружи, для компонента, нет никакой разницы в вызове синхронных и асинхронных действий (и компонентам больше не нужно об этом беспокоиться)
Таким образом, с помощью механизма промежуточных слоев, мы добавили неявный слой (a layer of indirection), который дал нам больше гибкости.
Поскольку redux-thunk передает в возвращаемые функции методы dispatch и getState из хранилища (store) как параметры, то вы можете отсылать другие действия и использовать состояние (state) для реализации дополнительной логики и workflow.
Но что если у нас есть что-то более сложное, чтобы быть выраженным с помощью преобразователя (thunk), без изменения react компонента. В этом случае мы можем попробовать использовать другую библиотеку промежуточных слоев (middleware library) и получить больше контроля.
Давайте посмотрим как заменить redux-thunk на библиотеку, что может дать нам больше контроля — redux-saga.
Redux-saga
Redux-saga это библиотека нацеленная делать сайд-эффекты проще и лучше путем работы с сагами.
Саги это дизайн паттерн, который пришел из мира распределенных транзакций, где сага управляет процессами, которые необходимо выполнять транзакционным способом, сохраняя состояние выполнения и компенсируя неудачные процессы.
Чтобы узнать больше о сагах можно начать с просмотра Применения паттерна Сага от Caitie McCaffrey, ну а если вы амбициозны, то здесь Статья, которая первая описывает саги в отношении распределенных систем.
В контексте Redux, сага реализована как промежуточный слой (мы не можем использовать редюсеры потому что они должны быть чистыми функциями), который координирует и побуждает асинхронные действия (сайд-эффекты).
Redux-saga делает это с помощью ES6 генераторов
Генераторы (Generators) это функции которые могут быть остановлены и продолжены, вместо выполнения всех выражений в один проход.
Когда вы вызываете функцию-генератор, она возвращает объект-итератор. И с каждым вызовом метода итератора next() тело функции-генератора будет выполняться до следующего yield выражения и затем останавливаться.
Это делает асинхронный код проще для написания и понимания.
Для примера вместо следующего выражения:
С генераторами мы бы написали так:
Возвращаясь к redux-saga, если говорить в общем, мы имеем сагу чья работа это следить за отправленными действиями (dispatched actions).
Для координирования логики, которую мы хотим реализовать внутри саги, мы можем использовать вспомогательную функцию takeEvery для создания новой саги для выполнения операции.
Если есть несколько запросов, takeEvery стартует несколько экземпляров саги-рабочего (worker saga). Иными словами реализует конкурентность(concurrency) для вас.
Надо отметить, что сага-наблюдатель (watcher saga) является другим неявным слоем (layer of indirection), который дает больше гибкости для реализации сложной логики (но это может быть лишним для простых приложений).
Теперь мы можем реализовать fetchDogAsync() функцию (мы полагаем, что у нас есть доступ к методу dispatch)
Но redux-saga позволяет нам получить объект, который декларирует наше намерение произвести операцию, вместо результата выполнения самой операции. Иными словами, пример выше реализуется в redux-saga следующим образом:
(Прим. переводчика: автор забыл заменить самый первый вызов dispatch)
Вместо вызова асинхронного реквеста напрямую, метод call вернет только объект описывающий эту операцию и redux-saga сможет позаботиться о вызове и возвращении результатов в функцию-генератор.
Тоже самое касается и метода put. Вместо отправления действий (dispatch action) внутри функции-генератора, put возвращает объект с инструкциями для промежуточного слоя (middleware) — отправить действие.
Эти возвращаемые объекты называются Эффекты (Effects). Ниже пример эффекта возвращаемого методом call:
Работая с Эффектами, redux-saga делает саги скорее Декларативными, чем Императивными.
Декларативное программирование это стиль программирования, который пытается минимизировать или устранить сайд-эффекты, описанием что программа должна делать, вместо описания как она должна это делать.
Преимущество, которое это дает, и о чем говорят большинство людей, то что функцию, которая возвращает простой объект, гораздо проще тестировать, чем функцию, которая делает асинхронный вызов. Для тестирования, вам не нужно использовать реальное АПИ, делать фейки или мокать.
Для тестирования, вы просто итерируете функцию-генератор делая assert и сравниваете полученные значения.
Еще одно дополнительное преимущество это возможность легко объединять разные эффекты в сложный workflow.
Возвращаясь к нашему простому примеру, ниже полная реализация в redux-saga:
Когда вы нажимаете на кнопку, вот что происходит:
1. Отправляется действие FETCHED_DOG
2. Сага-наблюдатель (watcher saga) watchFetchDog получает это действие и вызывает сагу-рабочего (worker saga) fetchDogAsync.
3. Отправляется действие по отображению индикатора загрузки.
4. Происходит вызов API метода.
5. Отправляется действие по обновлению состояния (успех или провал)
Если вы считаете, что несколько неявных слоев и чуть-чуть дополнительной работы стоят этого, то redux-saga может дать вам больше контроля для обработки сайд-эффектов функциональным способом.
Заключение
Эта статья показала как реализовать асинхронные операции в Redux с помощью генераторов действий (action creators), преобразователей (thunks), и саг (sagas), идя от простого подхода к более сложному.
Redux не предписывает решение для обработки сайд-эффектов. Когда вы будете решать какому подходу следовать, вам необходимо учитывать сложность вашего приложения. Моя рекомендация — начинать с простого решения.
Также есть альтернативы redux-saga, которые стоит попробовать. Две самых популярных это redux-observable (который базируется на RxJS) и redux-logic (также базирующийся на RxJS наблюдателях, но дающий свободу писать вашу логику в других стилях).
Зачем нужен redux-thunk. Где его использовать, а где можно обойтись и без него
Йо-йо. Так как я сейчас работаю frontend-разработчиком и пишу свой код на react’е мне приходится работать и с redux, чтобы хранить данные и как-то их изменять. Конечно же изменение этих данных влечёт новый рендер.
Из-за того, что данных бывает очень много и, порой, нужно сделать множество преобразований за одно действие, причём изменить данные или запросить их на сервере простого redux не хватает. Приходится пользоваться middleware redux-thunk. В этой статье я хочу поделиться своим опытом использования redux-thunk, рассказать где без него не обойтись, а где для лучшего UX его миновать.
Что такое middleware
Это какая-то функция, которая берёт текущий store, текущий action и что-то делает с этим. Более подробно лучше прочитать в документации.
Redux-thunk — это как раз такая функция, которая, что-то делает со store.
Зачем использовать redux-thunk
Если вы задались этим вопросом, значит вы уже пользуетесь redux и знаете, что обычно action является простым объектом, который используется в reduser, a action creator имеет примерно такой вид:
В данном примере данные будут переданы в переменной array, эти данные и получит reduser и передаст в store.
Пример 1
В примере выше всё очень просто, но часто мы ещё не имеем ни каких данных и их нужно запросить с сервера. Тут то нам и нужен middleware для того, чтобы вызвать dispatch в то время, когда данные уже будут получены. Тогда наш action creator примет такой вид:
В этой функции мы явно вызываем dispatсh, в тот момент, когда мы получим данные.
Пример 2
Кроме того, что мы не имеем данных, бывает, что нужно вызвать сразу несколько action’ов. Например вы хотите сделать ваш компонент неактивным, пока идёт запрос. Вы можете сделать несколько вызовов dispatch в одном экшене.
В данном примере в начале я вызываю другой action creator (setDetouchStadia), делаю запрос на сервер и в любом случает вызываю его заново.
Пример 3
Иногда за одно действие может быть вызвано огромное количество изменений. Сейчас (декабрь 2019) я работаю над плеером для музыки 8bit. Как у любого плеера у него есть пульт для управления треками и список треков, которые можно выбрать. В момент выбора трека мне нужно сделать множество действий, и знать кое-какие данные из store.
Как я уже говорил redux-thunk, как и другие middleware даёт нам доступ к store:
Переменная getState — это функция, которая возвращает текущий экземпляр store.
Алгоритм действий, которые я решил, чтобы выполнялся при выборе трека:
В итоге у меня получился вот такой action creator
Когда не нужно использовать redux-thunk
Как может показаться, каждый раз, когда мы используем запросы к серверу мы должны использовать его… А вот и нет.
Хороший пример у меня был совсем недавно в статье про getDerivedStateFromProps. Там говорилось об отмене подписке на уведомления. Там использовался хук реакта и state компонента для изменения состояния подписки и ожидание нового значения из store. Но можно пойти другим путём.
По сути нам нужно было изменить в redux данные об элемент, но если бы мы отправили запрос на сервер в action и ждали ответ сервера то казалось бы, что наш сайт тормозит. Следовательно нам нужно изменить данные и отослать запрос на сервер. И так
Как вы видите здесь не подразумевалось получение важных данных с сревера. По-этому лучше тут не использовать middleware если оно не нужно. Вполне возможно, что у вас есть только подобные случаи в приложении и не нужно увеличивать бандл без необходимости.
Ещё пример
Иногда нам нужно изменить данные опираясь на state и не нужно получать весь, store целиком. В этом случае тоже можно отказаться от thunk а за место него использовать функции в самом reduser’е. Как я показал выше.
Про react и redux стало на статью больше. Если вы не согласны с моим мнение то можете написать своё в комментариях. А если согласны то не постесняйтесь поддержать проект денежкой)
Поддержи Xakplant
Я давно хочу развить видеоверсию, но пока этого не получается из-за нехватки ресурсов. Сейчас я собираю деньги на новый компьютер и микрофон. Поддержи xaklant и ты увидишь полезные видео быстрее.
Асинхронные действия Redux с Redux Thunk
Published on December 1, 2020
Введение
По умолчанию действия Redux обрабатываются синхронно, что представляет проблему для любых нестандартных приложений, которым требуется взаимодействовать с внешними API или использовать побочные эффекты. Redux также позволяет использовать промежуточное ПО между обрабатываемым действием и действием, которое достигает редукторов.
Существует две очень популярные библиотеки промежуточного ПО, поддерживающие побочные эффекты и асинхронные действия: Redux Thunk и Redux Saga. В этой статье мы расскажем о Redux Thunk.
Thunk или преобразователь — это концепция программирования, в которой функция используется для отсрочки оценки или расчета операции.
Redux Thunk — это промежуточное ПО, позволяющее вызывать создателей действий, которые возвращают функцию вместо объекта действия. Эта функция получает метод обработки магазина, который затем используется для обработки регулярных синхронных действий внутри тела функции после выполнения асинхронных операций.
Из этой статьи вы узнаете, как добавить Redux Thunk и использовать его для гипотетического приложения Todo.
Предварительные требования
Данная статья предполагает, что у вас имеются базовые знания по React и Redux. Вы можете посмотреть эту статью, если только начинаете работать с Redux.
Здесь не разъясняются более подробные детали процедуры создания приложения Todo с нуля. Оно представлено как концептуальная основа для разъяснения Redux Thunk.
Добавление redux-thunk
Прежде всего, используйте терминал для перехода в каталог проекта и установите пакет redux-thunk в ваш проект:
Примечание. Redux Thunk содержит всего 14 строк кода. Посмотрите исходный код здесь, чтобы узнать о принципах работы промежуточного ПО Redux.
Итак, мы импортировали Redux Thunk и добавили его в наше приложение.
Использование Redux Thunk в образце приложения
Чаще всего Redux Thunk используется для асинхронного взаимодействия с внешним API с целью получения или сохранения данных. Redux Thunk упрощает обработку действий, сопровождающих жизненный цикл запроса внешнего API.
Для создания нового элемента todo обычно требуется предварительно обработать действие, чтобы обозначить начало создания элемента todo. Затем, если элемент todo успешно создан и возвращен внешним сервером, необходимо обработать другое действие с новым элементом todo. В случае получения ошибки и невозможности сохранения todo на сервере необходимо обработать действие с ошибкой.
Давайте посмотрим, как сделать это с помощью Redux Thunk.
Импортируйте действие в компонент контейнера и обработайте его:
Действие использует Axios для отправки запроса POST на конечную точку в JSONPlaceholder ( https://jsonplaceholder.typicode.com/todos ):
Обратите внимание на то, как создатель действия addTodo возвращает функцию вместо обычного объекта действия. Эта функция получает метод обработки из магазина.
При использовании внешнего API, например, JSONPlaceholder в приведенном примере, может возникнуть реальная задержка сети. Однако, если вы работаете с локальным сервером, ответ сети может быть получен слишком быстро, чтобы получить такую же задержку, с какой столкнется конечный пользователь, так что вы можете добавить искусственную задержку при разработке:
Чтобы протестировать сценарии ошибок, вы можете создать ошибку вручную:
Для полноты приведем пример того, как может выглядеть редуктор todo для обработки полного жизненного цикла запроса:
Изучение getState
После получения метода dispatch от состояния функция, возвращаемая создателем асинхронного действия с Redux Thunk, также получает метод getState магазина, что позволяет прочитать текущие значения магазина:
В вышеперечисленном случае текущее состояние просто распечатывается на консоли.
Использование getState может быть полезно для того, чтобы обрабатывать действия по-разному в зависимости от текущего состояния. Например, если вы захотите ограничить возможности приложения одновременной обработкой только четырех элементов todo, вы можете выполнять возврат из функции, если состояние уже содержит максимальное количество элементов todo:
В вышеуказанном случае приложение сможет работать только с четырьмя элементами todo.
Заключение
В этом учебном модуле мы изучили добавление Redux Thunk в приложение React для асинхронной обработки действий. Это полезно при использовании магазина Redux и внешних API.
Зачем нужен redux-thunk?
Сегодня с коллегой возникла небольшая дискуссия, я утверждал, что redux-thunk очень удобная надстройка (middleware) над redux и позволяет создавать асинхронные экшены.
Коллега возразил, зачем нужен redux-thunk, когда можно сделать вот так:
Т.е он импортит store в определенный файл проекта и вызывает несколько экшенов
По началу я впал в ступор, действительно, зачем redux-thunk, если можно испортить стор, но позже проанализировав ситуацию, пришел к выводу, что асинхронные экшены, например как в примере выше загрузка профиля должна иметь 3 состояния:
— старт загрузки профиля (для отображения лоадера)
— данные профиля успешно загружены
— возникла ошибка при загрузке данных
исходя из этого, если действовать по методу коллеги, необходимо создавать для каждого действия отдельную функцию (экшены)
Вот пример с использованием redux-thunk
Преимущества redux-thunk очевидны, и больше всего мне нравится то, что с помощью redux-thunk всю логику можно хранить в экшенах и тем самым разгрузить компоненты, это одновременно делает проект чище, ведь логика будет находится внутри экшенов, вместо того, чтобы импортить стор в разные части проекта
Что вы скажите, какое мнение верно, и имеет ли права на жизнь предложенное коллегой решение?
Если я правильно понял вашего коллегу, то речь была не о том, что нужно асинхронные функции через setTimeout вызывать, а это был просто пример, как сделать какой-то кусочек «типа асинхронным».
Его довод был в том, что вместо вызова dispatch из замыкания (полученного с помощью redux-thunk, например) он вызывал store.dispatch напрямую (то есть у объекта store, и этот store импортировал бы в каждом файле).
В остальном, все очень четко расписано в ответе Дэна Абрамова, который привел holymotion.
Если быть кратким, то вам нужна функция dispatch, ведь именно через нее вы «диспатчите» свои экшены. Вы не можете в асинхронном ответе написать:
Поэтому вы были бы обязаны в каждый action creator, который является асинхронным, передавать бы помимо нужных вам аргументов, еще и функцию dispatch из своего контейнера. Что не удобно. (это все есть по ссылке на stackoverflow, но более подробно)