для чего нужен redux thunk

Разбираемся в redux-saga: От генераторов действий к сагам

для чего нужен redux thunk

Любой redux разработчик расскажет вам, что одной из самых тяжелейших частей разработки приложений являются асинхронные вызовы — как вы будете обрабатывать реквесты, таймауты и другие коллбэки без усложнения redux действий(actions) и редьюсеров(reducers).

В этой статье я опишу несколько различных подходов к управлению асинхронностью в вашем приложении, начиная от простых подходов как redux-thunk, заканчивая более продвинутыми библиотеками вроде redux-saga.

Мы собираемся использовать React и Redux, поэтому будем полагать, что вы имеете хотя бы какое то представление о том как они работают.

Генераторы действий (Action creators)

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

для чего нужен redux thunk

мы можем использовать 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

для чего нужен 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 генераторов

для чего нужен redux thunk

Генераторы (Generators) это функции которые могут быть остановлены и продолжены, вместо выполнения всех выражений в один проход.

Когда вы вызываете функцию-генератор, она возвращает объект-итератор. И с каждым вызовом метода итератора next() тело функции-генератора будет выполняться до следующего yield выражения и затем останавливаться.

для чего нужен redux thunk

Это делает асинхронный код проще для написания и понимания.
Для примера вместо следующего выражения:

для чего нужен redux thunk

С генераторами мы бы написали так:

для чего нужен redux thunk

Возвращаясь к redux-saga, если говорить в общем, мы имеем сагу чья работа это следить за отправленными действиями (dispatched actions).

для чего нужен redux thunk

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

для чего нужен redux thunk

Если есть несколько запросов, takeEvery стартует несколько экземпляров саги-рабочего (worker saga). Иными словами реализует конкурентность(concurrency) для вас.

Надо отметить, что сага-наблюдатель (watcher saga) является другим неявным слоем (layer of indirection), который дает больше гибкости для реализации сложной логики (но это может быть лишним для простых приложений).

Теперь мы можем реализовать fetchDogAsync() функцию (мы полагаем, что у нас есть доступ к методу dispatch)

для чего нужен redux thunk

Но redux-saga позволяет нам получить объект, который декларирует наше намерение произвести операцию, вместо результата выполнения самой операции. Иными словами, пример выше реализуется в redux-saga следующим образом:

для чего нужен redux thunk

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

Тоже самое касается и метода put. Вместо отправления действий (dispatch action) внутри функции-генератора, put возвращает объект с инструкциями для промежуточного слоя (middleware) — отправить действие.

Эти возвращаемые объекты называются Эффекты (Effects). Ниже пример эффекта возвращаемого методом call:

для чего нужен redux thunk

Работая с Эффектами, redux-saga делает саги скорее Декларативными, чем Императивными.

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

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

Для тестирования, вы просто итерируете функцию-генератор делая assert и сравниваете полученные значения.

для чего нужен redux thunk
Еще одно дополнительное преимущество это возможность легко объединять разные эффекты в сложный 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 экшены используя 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 работает под капотом

Источник

Writing Logic with Thunks

Thunk Overview​

What is a «thunk»?​

The word «thunk» is a programming term that means «a piece of code that does some delayed work». Rather than execute some logic now, we can write a function body or code that can be used to perform the work later.

For Redux specifically, «thunks» are a pattern of writing functions with logic inside that can interact with a Redux store’s dispatch and getState methods.

Using thunks requires the redux-thunk middleware to be added to the Redux store as part of its configuration.

Thunks are the standard approach for writing async logic in Redux apps, and are commonly used for data fetching. However, they can be used for a variety of tasks, and can contain both synchronous and asynchronous logic.

Writing Thunks​

A thunk function is a function that accepts two arguments: the Redux store dispatch method, and the Redux store getState method. Thunk functions are not directly called by application code. Instead, they are passed to store.dispatch() :

A thunk function may contain any arbitrary logic, sync or async, and can call dispatch or getState at any time.

In the same way that Redux code normally uses action creators to generate action objects for dispatching instead of writing action objects by hand, we normally use thunk action creators to generate the thunk functions that are dispatched. A thunk action creator is a function that may have some arguments, and returns a new thunk function. The thunk typically closes over any arguments passed to the action creator, so they can be used in the logic:

In either case, the thunk is dispatched by calling the action creator, in the same way as you’d dispatch any other Redux action:

Why Use Thunks?​

Thunks allow us to write additional Redux-related logic separate from a UI layer. This logic can include side effects, such as async requests or generating random values, as well as logic that requires dispatching multiple actions or access to the Redux store state.

Redux reducers must not contain side effects, but real applications require logic that has side effects. Some of that may live inside components, but some may need to live outside the UI layer. Thunks (and other Redux middleware) give us a place to put those side effects.

It’s common to have logic directly in components, such as making an async request in a click handler or a useEffect hook and then processing the results. However, it’s often necessary to move as much of that logic as possible outside the UI layer. This may be done to improve testability of the logic, to keep the UI layer as thin and «presentational» as possible, or to improve code reuse and sharing.

In a sense, a thunk is a loophole where you can write any code that needs to interact with the Redux store, ahead of time, without needing to know which Redux store will be used. This keeps the logic from being bound to any specific Redux store instance and keeps it reusable.

Detailed Explanation: Thunks, Connect, and «Container Components»

Thunk Use Cases​

Because thunks are a general-purpose tool that can contain arbitrary logic, they can be used for a wide variety of purposes. The most common use cases are:

Thunks are «one-shot» functions, with no sense of a lifecycle. They also cannot see other dispatched actions. So, they should not generally be used for initializing persistent connections like websockets, and you can’t use them to respond to other actions.

Thunks are best used for complex synchronous logic, and simple to moderate async logic such as making a standard AJAX request and dispatching actions based on the request results.

Redux Thunk Middleware​

Dispatching thunk functions requires that the redux-thunk middleware has been added to the Redux store as part of its configuration.

Adding the Middleware​

If you need to add the thunk middleware to a store manually, that can be done by passing the thunk middleware to applyMiddleware() as part of the setup process.

How Does the Middleware Work?​

To start, let’s review how Redux middleware work in general.

With that in mind, we can look at the specifics of the thunk middleware.

Injecting Config Values Into Thunks​

The thunk middleware does have one customization option. You can create a custom instance of the thunk middleware at setup time, and inject an «extra argument» into the middleware. The middleware will then inject that extra value as the third argument of every thunk function. This is most commonly used for injecting an API service layer into thunk functions, so that they don’t have hardcoded dependencies on the API methods:

There can only be one extra argument value. If you need to pass in multiple values, pass in an object containing those.

The thunk function will then receive that extra value as its third argument:

Thunk Usage Patterns​

Dispatching Actions​

Thunks have access to the dispatch method. This can be used to dispatch actions, or even other thunks. This can be useful for dispatching multiple actions in a row (although this is a pattern that should be minimized), or orchestrating complex logic that needs to dispatch at multiple points in the process.

Accessing State​

It’s preferable to put as much logic as possible in reducers, but it’s fine for thunks to also have additional logic inside as well.

Since the state is synchronously updated as soon as the reducers process an action, you can call getState after a dispatch to get the updated state.

One other reason to consider accessing state in a thunk is to fill out an action with additional info. Sometimes a slice reducer really needs to read a value that isn’t in its own slice of state. A possible workaround to that is to dispatch a thunk, extract the needed values from state, and then dispatch a plain action containing the additional info.

Async Logic and Side Effects​

When making async requests, it’s standard to dispatch actions before and after a request to help track loading state. Typically, a «pending» action before the request and a loading state enum is marked as «in progress». If the request succeeds, a «fulfilled» action is dispatched with the result data, or a «rejected» action is dispatched containing the error info.

This pattern is admittedly awkward to write and read. In most cases you can probably get away with a more typical try/catch pattern where the request and the dispatch(requestSucceeded()) are back-to-back. It’s still worth knowing that this can be an issue.

Returning Values from Thunks​

The thunk middleware does this, by returning whatever the called thunk function returns.

The most common use case for this is returning a promise from a thunk. This allows the code that dispatched the thunk to wait on the promise to know that the thunk’s async work is complete. This is often used by components to coordinate additional work:

This is not a recommended practice per se, but it’s semantically legal and will work fine.

Using createAsyncThunk ​

Writing async logic with thunks can be somewhat tedious. Each thunk typically requires defining three different action types + matching action creators for «pending/fulfilled/rejected», plus the actual thunk action creator + thunk function. There’s also the edge cases with error handling to deal with.

Since this is an abstraction for the specific use case of async requests, createAsyncThunk does not address all possible use cases for thunks. If you need to write synchronous logic or other custom behavior, you should still write a «normal» thunk by hand yourself instead.

Fetching Data with RTK Query​

Redux Toolkit has a new RTK Query data fetching API. RTK Query is a purpose built data fetching and caching solution for Redux apps, and can eliminate the need to write any thunks or reducers to manage data fetching.

RTK Query actually uses createAsyncThunk internally for all requests, along with a custom middleware to manage cache data lifetimes.

First, create an «API slice» with definitions for the server endpoints your app will talk to. Each endpoint will auto-generate a React hook with a name based on the endpoint and request type, like useGetPokemonByNameQuery :

Then, add the generated API slice reducer and custom middleware to the store:

Finally, import the auto-generated React hook into your component and call it. The hook will automatically fetch data when the component mounts, and if multiple components use the same hook with the same arguments, they will share the cached results:

We encourage you to try out RTK Query and see if it can help simplify the data fetching code in your own apps.

Источник

для чего нужен redux thunk

Всем привет. Раньше мы с вами разбирали как работают екшены в redux. Мы разбирали только синхронные екшены, которые выполняются моментально. Что делать с екшенами, который выполняются асинхронно? Например получение данные от API. В этом случае нам необходима специальная middleware, которая позволит нам писать асинхронные екшены в redux.

В redux мы создавали новый store командой createStore и передавали в нее первым аргументом рут reducer. Вторым аргументом в нее можно передавать дополнительные вещи, которые будут улучшать работу с redux. Например redux-devtools или миддлвары.

У нас вторым параметром идет redux-devtools и нам нужно применить как devtools, так и нашу middleware.

Для этого установим пакет

импортируем composeWithDevTools из redux-devtools-extension, applyMiddleware из redux и thunk из redux-thunk.

И изменим второй аргумент.

Что мы тут сделали? Метод composeWithDevTools это улучшеный метод compose, который автоматически добавляет devtools к всему, что мы передали ему внутрь. Внутри мы вызываем applyMiddleware, которая принимает в качесте аргументов middleware и применяет их. То есть теперь если мы захотим добавить еще какую-то middleware, мы просто добавим ее через запятую. Пока же мы применили только middleware thunk для асинхронных екшенов.

Такой код обычно дублируется из проекта в проект и особо не отличается.

Теперь давайте напишем с вами кнопку, нажав которую, мы будем получать асинхронно список треков, как будто от API и рендерить их вместо наших треков на странице.

Добавим кнопку Get tracks.

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

Давайте опишем этот метод в mapStateToDispatch.

Давайте разбираться. Как обычно мы вызываем dispatch. Но теперь мы передаем в него не обьект, а функцию, которая возвращает функцию. И в возвращаемой функции есть аргумент dispatch. Теперь мы можем в этой функции делать любые асинхронные операции и вызывать dispatch тогда, когда нам нужно. У нас есть setTimeout с таймаутом 2 секунды и внутри него задиспатчим новый евент.

Я знаю, что пока все выглядит очень запутанно, но скоро мы с вами это упростим. Пока давайте проверим, что в браузере у нас все работает. У нас выводится console.log и в redux-devtools мы видим с вами action.

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

Вынесем нашу асинк функцию и заекспрортим ее.

Теперь в App.js импортируем этот екшен и вызовем его в onGetTracks.

Код в нашей компоненте стал понятнее в onGetTracks мы просто диспатчим екшен getTracks и нас не интересует, что внутри него происходит.

Давайте проверим, что все по прежнему работает.

Теперь мы можем избавиться от 1 уровня вложености в нашем екшене, чтобы было читабельнее.

Мы просто использовали короткую запись, чтобы описать, что метод getTracks возвращает функцию с аргументом dispatch.

Теперь наш екшен выглядит достаточно лаконично. У нас происходит setTimeout и в коллбеке мы диспатчим екшен, что мы успешно получили треки.

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

Но наш редьюсер все еще не слушает екшен FETCH_TRACKS_SUCCESS. Для этого просто добавим это условие в редьюсер и перезапишем наш стейт.

Давайт помотрим. Теперь при нажатии на кнопку Get tracks у нас происходит асинхронный екшен и когда он успешно завершился, мы рендерим треки.

Итак, в этом уроке мы с вами научились писать асинхронные екшены с помощью мидлвары redux-thunk, а также узнали как настроить redux так, чтобы он работал одновременно с devtools и middlewares.

Источник


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *