Redux middleware что это
Перейти к содержимому

Redux middleware что это

  • автор:

Совершенствуем Redux

Привет, Хабр! Представляю вашему вниманию перевод статьи «Redesigning Redux» автора Shawn McKay.

Должно ли управление состоянием вызывать проблемы на сегодняшний день? Интуитивно, разработчики видят скрытую правду: управление состоянием куда сложнее, чем должно быть. В данной статье мы разберем несколько вопросов, которые вы наверняка задавали себе:

  • Вам действительно необходима библиотека для управления состоянием?
  • Заслужил ли Redux свою популярность? Почему или почему нет?
  • Можем ли мы придумать лучшее решение? Если да, то какое?

Необходима ли библиотека для управления состоянием?

Front-end разработчик не тот, кто попросту передвигает пиксели из стороны в сторону; истинное искусство в знании где хранить состояние. Это кажется сложным только на первый взгляд.

Давайте рассмотрим возможности, которые предоставляет нам React:

image

1. Состояние компонента (Component State)

Состояние хранится внутри компонента. В React мы обновляем state через setState() .

2. Относительное состояние (Relative State)

Состояние переданное от родителя потомку. В React передаем props как свойство компонента потомка.

3. Переданное состояние (Provided State)

Состояние хранится в поставщике (provider), и доступно любому компоненту (consumer), расположенному ниже по дереву. Context API в React.

View хранит большую часть состояния. Но что делать с остальным кодом, который отражает основные данные и логику?

Размещение всего кода внутри компонентов может привести к низкому разделению ответственности: растет зависимость от view-библиотек, усложняется тестирование такого кода, но самое страшное: приходится регулярно менять способ хранения состояния.

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

4. Внешнее состояние (External State)

Состояние может находиться отдельно от компонентов, которые синхронно «свяжуться» с ним при помощи provider/consumer паттерна. Вероятнее всего самой большой популярностью, среди библиотек для управления состоянием, пользуется Redux. В течении последних двух лет она получила большую известность среди разработчиков. Так в чем причина такой любви к одной библиотеке?

Redux более производительный? Нет. На самом деле, работа приложения немного замедляется с каждым новым действием, которое должно быть обработано.

Redux прост в применении? Конечно нет.

Простым был бы нативный javascript:

image

Так почему каждый не может использовать global.state = <> ?

Почему Redux?

Под капотом, Redux аналогичен глобальному объекту TJ, только обернут рядом утилит.

image

В Redux можно непосредственно изменять состояние, путем передачи (dispatch) действий (action) через указанные инструменты.

Библиотека включает два вида обработчиков действий: middleware & subscriptions. Middleware — это функции, которые перехватывают действия. Включают такие инструменты как «logger», «devtools» или «syncWithServer». Subscriptions — это функции, используемые для отправки изменений компонентам.

Наконец, редьюсеры (reducer) — это функции, которые изменяют состояние и делят его на мелкие, модульные и управляемые части.

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

Думайте о Redux как о глобальном объекте с расширенными возможностями и упрощенным способом «преобразования» состояния.

Настолько ли сложен Redux?

Да. Есть несколько неоспоримых признаков, что необходимо улучшить API; можно сделать вывод при помощи следующего уравнения:

Считаем, что time_saved подразумевает время затраченное на разработку собственного решения, а time_invested равняется часам потраченным на чтение документации, прохождение обучающих курсов и изучение новых понятий.

Redux, в принципе, простая и небольшая библиотека с крутой кривой обучения. На каждого разработчика, который овладел и извлек выгоду из Redux, погрузившись в функциональное программирование, найдется другой потенциальный разработчик, запутавшийся и думающий «это все не для меня, я возвращаюсь к jQuery».

Вы не должны разбираться что такое «comonad», используя jQuery, и не обязаны понимать функциональную композицию, чтобы справиться с управлением состоянием.

Цель любой библиотеки: сделать сложное простым при помощи абстракции.

Я не намерен высмеивать Дэна Абрамова. Redux стал популярным на слишком ранней стадии своего развития.

  • Как внести изменения в библиотеку, которую используют миллионы разработчиков?
  • Как вы оправдаете критические изменения, которые повлияют на проекты во всем мире?

А может есть другой путь?

Совершенствуем Redux

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

1. Настройка

Предлагаю посмотреть на первоначальную настройку Redux-приложения (левый скрин).

Много разработчиков, сразу после первого шага, остановились в недоумении. Что такое thunk? compose? Способна ли функция на такое?

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

2. Упрощаем редьюсеры

Редьюсеры в Redux могут использовать switch-конструкции далекие от тех, которые мы привыкли использовать.

Учитывая, что редьюсеры находят соответствие по типу действия, мы можем сделать каждый редьюсер чистой (pure) функцией, принимающей состояние и действие. Можно сократить действие и передавать только состояние и данные.

3. Async/Await без Thunk

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

  1. Вы передаете действие, уже как функцию, а не объект.
  2. Thunk проверяет каждое действие, что оно является функцией.
  3. Если все сходится, thunk вызывает эту функцию и передает в нее некоторые методы стора: dispatch и getState.

Возможно ли использовать async/await, как в примере справа?

4. Два вида действий

Задумайтесь, ведь действительно есть два вида действий:

  1. Reducer action: запускает редьюсер и изменяет состояние.
  2. Effect action: запускает асинхронное действие. Может вызвать reducer action, но асинхронная функция не способна напрямую изменить состояние.
5. Никаких больше переменных хранящих тип действия

Почему принято разделять генераторы действий (action creators) и редьюсеры? Может ли один существовать без другого? Как изменить один не изменяя другой?

Генераторы действий и редьюсеры две стороны одной медали.

const ACTION_ONE = ‘ACTIONE_ONE’ — это лишний побочный эффект разделения генераторов действий и редьюсеров. Обращайтесь с ними как с единым целом и отпадет потребность в крупных файлах с экспортом типов.

6. Редьюсеры — это генераторы действий

Объединяйте элементы Redux по их назначению, и вы получите простой шаблон.

В итоге, следуя этому сценарию, редьюсер может стать генератором действий.

Используйте соглашение об именах, и следующие пункты будут довольно предсказуемы:

  1. Если редьюсер получил название «increment», тогда тип будет «increment». Даже лучше, обозначим как «count/increment».
  2. Каждое действие передает данные через «payload».

Теперь при помощи count.increment мы можем создать генератор действий напрямую из редьюсера.

Хорошие новости: мы можем улучшить Redux

Эти проблемные части мотивировали на создание Rematch.

Rematch служит оберткой вокруг Redux, предоставляя разработчикам упрощенное API.

Вот полный пример кода с Rematch:

Я использовал Rematch в production последние несколько месяцев. И что я думаю:

Я никогда не тратил так мало времени на управление состоянием.

Redux не исчезнет и не обязан. Освойте эту библиотеку с меньшей кривой обучения, меньшим количеством бойлерплейта и меньшими умственными затратами.

Опробуйте Rematch и выясните, нравится он вам или нет.
Поставьте звезду, чтобы позволить узнать о нас другим.

Пишем свой middleware для Redux

Доброго времени суток, друзья. В данной статье мы разберем понятие middleware в Redux и напишем свою небольшую Redux-middleware для логирования. Давайте обо всем по порядку.

Видео на эту тему.

Что такое middleware и зачем они нужны?

Само понятие middleware довольно-таки старая концепция. Если посмотреть на понятие в общем, то это промежуточное программное обеспечение для среднего слоя программы.

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

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

Благодаря middleware в Redux имеется замечательная возможность производить нужные промежуточные действия в момент, когда пользователь взаимодействует с интерфейсом. Например, мы можем логировать данные, получать дополнительные данные, создавать нужные задержки и тд.

Пишем свою миделвару Logger для Redux

Давайте перейдем наконец к практике и напишем свою миделвару. Для этого создадим функцию logger, которая принимает в параметрах весь store. Функция logger будет возвращать анонимную функцию и получать в аргументе функцию next, которая на самом деле является функцией dispatch. После чего нам нужно вернуть функцию next с переданным в нее параметром action. Звучит немного запутанно, поэтому давайте опишем эту конструкцию в коде, используя ES5.

В самой функции нам потребуется воспроизвести следующую логику:

1. Проверяем начальное состояние store

2. Проверяем action

3. Передаем через параметр функции next полученный action для передачи его редьюсеру

4. Снова проверяем store

5. Возвращаем результат next(action) из функции logger

 var logger = function logger(store) < return function (next) < return function (action) < console.log("dispatching", action.type); console.log("prev state", store.getState()); console.log("action", action); result = next(action); console.log("next state", store.getState()); return result; >; >; >; 

У вас может появиться вопрос: «Откуда в аргументах logger вдруг появился доступ к store, функциям next и action?» Все благодаря функции applyMiddleware и замыканию. Каждый последующий вызов функции logger получает из области видимости функции applyMiddleware новые параметры, которые можно использовать внутри самой миделвары.

Благодаря стрелочным функциям и замыканию можно более компактно описать данную конструкцию.

 const logger = store => next => action => < let result; console.groupCollapsed("dispatching", action.type); console.log("prev state", store.getState()); console.log("action", action); result = next(action); console.log("next state", store.getState()); console.groupEnd(); return result; >; 

В коде выше для визуальной группировки логов используются не очень распространенные функции console.groupCollapsed() и console.groupEnd().

Как добавить миделвару в Redux?

Чтобы инициализировать миделвару logger в приложении, нужно добавить ее в функцию applyMiddleware, которая импортируется из самого redux, и передать вторым параметром в createStore.

 const middleware = applyMiddleware(logger); const store = createStore(reducers, middleware); 

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

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

Небольшое отступление: если вы решили написать свою собственную миделвару, вы можете посмотреть на уже реализованные (например тут ) и использовать их, так как с большей вероятностью вашу проблему уже решили другие разработчики.

Пример нескольких middleware

Для большей наглядности приведу ниже несколько примеров Redux-middleware, которые могут быть вам полезны.

1. Позволяет отправлять промисы с экшеном. Если промис resolve, то его ответ будет отправлен как action.

 const promiseMiddleware = store => next => action => < if (typeof action.then !== 'function') < return next(action) >return Promise.resolve(action).then(store.dispatch) > 

2. Позволяет использовать функцию вместо экшена. Данная миделвар является очень популярной и велика вероятность, что вы с ней уже знакомы.

 const thunkMiddleware = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action) 

3. Позволяет отправлять action, в который можно передать количество миллисекунд в поле meta.delay для задержки выполнения данного экшена.

 const schedulerMiddleware = store => next => action => < if (!action.meta || !action.meta.delay) < return next(action) >const () => next(action), action.meta.delay ) return function cancel() < clearTimeout(id) >> 

Заключение

Сегодня мы рассмотрели подробно понятие middleware. Написали свою Redux-middleware для логирования экшенов и стора. В статье приведены примеры кода нескольких миделвар которые могут использоваться в вашей ежедневной работе или для образовательных целей. Надеюсь, что данный материал был вам полезен. Учитесь, думайте, пишите код. Удачного кодинга, друзья!

Подписывайтесь на наш канал в Telegram и на YouTube для получения самой последней и актуальной информации.

Middleware

You’ve seen middleware in action in the «Redux Fundamentals» tutorial. If you’ve used server-side libraries like Express and Koa, you were also probably already familiar with the concept of middleware. In these frameworks, middleware is some code you can put between the framework receiving a request, and the framework generating a response. For example, Express or Koa middleware may add CORS headers, logging, compression, and more. The best feature of middleware is that it’s composable in a chain. You can use multiple independent third-party middleware in a single project.

Redux middleware solves different problems than Express or Koa middleware, but in a conceptually similar way. It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.

This article is divided into an in-depth intro to help you grok the concept, and a few practical examples to show the power of middleware at the very end. You may find it helpful to switch back and forth between them, as you flip between feeling bored and inspired.

Understanding Middleware​

While middleware can be used for a variety of things, including asynchronous API calls, it’s really important that you understand where it comes from. We’ll guide you through the thought process leading to middleware, by using logging and crash reporting as examples.

Problem: Logging​

One of the benefits of Redux is that it makes state changes predictable and transparent. Every time an action is dispatched, the new state is computed and saved. The state cannot change by itself, it can only change as a consequence of a specific action.

Wouldn’t it be nice if we logged every action that happens in the app, together with the state computed after it? When something goes wrong, we can look back at our log, and figure out which action corrupted the state.

How do we approach this with Redux?

Attempt #1: Logging Manually​

The most naïve solution is just to log the action and the next state yourself every time you call store.dispatch(action) . It’s not really a solution, but just a first step towards understanding the problem.

Note​

If you’re using react-redux or similar bindings, you likely won’t have direct access to the store instance in your components. For the next few paragraphs, just assume you pass the store down explicitly.

Say, you call this when creating a todo:

store.dispatch(addTodo('Use Redux')) 

To log the action and state, you can change it to something like this:

const action = addTodo('Use Redux')  console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) 

This produces the desired effect, but you wouldn’t want to do it every time.

Attempt #2: Wrapping Dispatch​

You can extract logging into a function:

function dispatchAndLog(store, action)   console.log('dispatching', action)  store.dispatch(action) console.log('next state', store.getState()) > 

You can then use it everywhere instead of store.dispatch() :

dispatchAndLog(store, addTodo('Use Redux')) 

We could end this here, but it’s not very convenient to import a special function every time.

Attempt #3: Monkeypatching Dispatch​

What if we just replace the dispatch function on the store instance? The Redux store is a plain object with a few methods, and we’re writing JavaScript, so we can just monkeypatch the dispatch implementation:

const next = store.dispatch store.dispatch = function dispatchAndLog(action)   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result > 

This is already closer to what we want! No matter where we dispatch an action, it is guaranteed to be logged. Monkeypatching never feels right, but we can live with this for now.

Problem: Crash Reporting​

What if we want to apply more than one such transformation to dispatch ?

A different useful transformation that comes to my mind is reporting JavaScript errors in production. The global window.onerror event is not reliable because it doesn’t provide stack information in some older browsers, which is crucial to understand why an error is happening.

Wouldn’t it be useful if, any time an error is thrown as a result of dispatching an action, we would send it to a crash reporting service like Sentry with the stack trace, the action that caused the error, and the current state? This way it’s much easier to reproduce the error in development.

However, it is important that we keep logging and crash reporting separate. Ideally we want them to be different modules, potentially in different packages. Otherwise we can’t have an ecosystem of such utilities. (Hint: we’re slowly getting to what middleware is!)

If logging and crash reporting are separate utilities, they might look like this:

function patchStoreToAddLogging(store)   const next = store.dispatch  store.dispatch = function dispatchAndLog(action)   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result > >  function patchStoreToAddCrashReporting(store)   const next = store.dispatch  store.dispatch = function dispatchAndReportErrors(action)   try   return next(action) > catch (err)   console.error('Caught an exception!', err) Raven.captureException(err,   extra:   action, state: store.getState() > >) throw err > > > 

If these functions are published as separate modules, we can later use them to patch our store:

patchStoreToAddLogging(store) patchStoreToAddCrashReporting(store) 

Still, this isn’t nice.

Attempt #4: Hiding Monkeypatching​

Monkeypatching is a hack. “Replace any method you like”, what kind of API is that? Let’s figure out the essence of it instead. Previously, our functions replaced store.dispatch . What if they returned the new dispatch function instead?

function logger(store)   const next = store.dispatch  // Previously: // store.dispatch = function dispatchAndLog(action)   return function dispatchAndLog(action)   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result > > 

We could provide a helper inside Redux that would apply the actual monkeypatching as an implementation detail:

function applyMiddlewareByMonkeypatching(store, middlewares)   middlewares = middlewares.slice()  middlewares.reverse()  // Transform dispatch function with each middleware.  middlewares.forEach(middleware => (store.dispatch = middleware(store))) > 

We could use it to apply multiple middleware like this:

applyMiddlewareByMonkeypatching(store, [logger, crashReporter]) 

However, it is still monkeypatching. The fact that we hide it inside the library doesn’t alter this fact.

Attempt #5: Removing Monkeypatching​

Why do we even overwrite dispatch ? Of course, to be able to call it later, but there’s also another reason: so that every middleware can access (and call) the previously wrapped store.dispatch :

function logger(store)   // Must point to the function returned by the previous middleware: const next = store.dispatch  return function dispatchAndLog(action)   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result > > 

It is essential to chaining middleware!

If applyMiddlewareByMonkeypatching doesn’t assign store.dispatch immediately after processing the first middleware, store.dispatch will keep pointing to the original dispatch function. Then the second middleware will also be bound to the original dispatch function.

But there’s also a different way to enable chaining. The middleware could accept the next() dispatch function as a parameter instead of reading it from the store instance.

function logger(store)   return function wrapDispatchToAddLogging(next)   return function dispatchAndLog(action)   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result > > > 

It’s a “we need to go deeper” kind of moment, so it might take a while for this to make sense. The function cascade feels intimidating. ES6 arrow functions make this currying easier on eyes:

const logger = store => next => action =>   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result >  const crashReporter = store => next => action =>   try   return next(action) > catch (err)   console.error('Caught an exception!', err) Raven.captureException(err,   extra:   action, state: store.getState() > >) throw err > > 

This is exactly what Redux middleware looks like.

Now middleware takes the next() dispatch function, and returns a dispatch function, which in turn serves as next() to the middleware to the left, and so on. It’s still useful to have access to some store methods like getState() , so store stays available as the top-level argument.

Attempt #6: Naïvely Applying the Middleware​

Instead of applyMiddlewareByMonkeypatching() , we could write applyMiddleware() that first obtains the final, fully wrapped dispatch() function, and returns a copy of the store using it:

// Warning: Naïve implementation! // That's *not* Redux API. function applyMiddleware(store, middlewares)   middlewares = middlewares.slice()  middlewares.reverse() let dispatch = store.dispatch  middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch))) return Object.assign(>, store,  dispatch >) > 

The implementation of applyMiddleware() that ships with Redux is similar, but different in three important aspects:

  • It only exposes a subset of the store API to the middleware: dispatch(action) and getState() .
  • It does a bit of trickery to make sure that if you call store.dispatch(action) from your middleware instead of next(action) , the action will actually travel the whole middleware chain again, including the current middleware. This is useful for asynchronous middleware. There is one caveat when calling dispatch during setup, described below.
  • To ensure that you may only apply middleware once, it operates on createStore() rather than on store itself. Instead of (store, middlewares) => store , its signature is (. middlewares) => (createStore) => createStore .

Because it is cumbersome to apply functions to createStore() before using it, createStore() accepts an optional last argument to specify such functions.

Caveat: Dispatching During Setup​

While applyMiddleware executes and sets up your middleware, the store.dispatch function will point to the vanilla version provided by createStore . Dispatching would result in no other middleware being applied. If you are expecting an interaction with another middleware during setup, you will probably be disappointed. Because of this unexpected behavior, applyMiddleware will throw an error if you try to dispatch an action before the set up completes. Instead, you should either communicate directly with that other middleware via a common object (for an API-calling middleware, this may be your API client object) or waiting until after the middleware is constructed with a callback.

The Final Approach​

Given this middleware we just wrote:

const logger = store => next => action =>   console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result >  const crashReporter = store => next => action =>   try   return next(action) > catch (err)   console.error('Caught an exception!', err) Raven.captureException(err,   extra:   action, state: store.getState() > >) throw err > > 

Here’s how to apply it to a Redux store:

import  createStore, combineReducers, applyMiddleware > from 'redux'  const todoApp = combineReducers(reducers) const store = createStore(  todoApp, // applyMiddleware() tells createStore() how to handle middleware applyMiddleware(logger, crashReporter) ) 

That’s it! Now any actions dispatched to the store instance will flow through logger and crashReporter :

// Will flow through both logger and crashReporter middleware! store.dispatch(addTodo('Use Redux')) 

Seven Examples​

If your head boiled from reading the above section, imagine what it was like to write it. This section is meant to be a relaxation for you and me, and will help get your gears turning.

Each function below is a valid Redux middleware. They are not equally useful, but at least they are equally fun.

/**  * Logs all actions and states after they are dispatched.  */ const logger = store => next => action =>   console.group(action.type) console.info('dispatching', action) let result = next(action) console.log('next state', store.getState()) console.groupEnd() return result >  /**  * Sends crash reports as state is updated and listeners are notified.  */ const crashReporter = store => next => action =>   try   return next(action) > catch (err)   console.error('Caught an exception!', err) Raven.captureException(err,   extra:   action, state: store.getState() > >) throw err > >  /**  * Schedules actions with < meta: < delay: N >> to be delayed by N milliseconds.  * Makes `dispatch` return a function to cancel the timeout in this case.  */ const timeoutScheduler = store => next => action =>   if (!action.meta || !action.meta.delay)   return next(action) >  const timeoutId = setTimeout(() => next(action), action.meta.delay)  return function cancel()   clearTimeout(timeoutId) > >  /**  * Schedules actions with < meta: < raf: true >> to be dispatched inside a rAF loop  * frame. Makes `dispatch` return a function to remove the action from the queue in  * this case.  */ const rafScheduler = store => next =>   const queuedActions = [] let frame = null  function loop()   frame = null try   if (queuedActions.length)   next(queuedActions.shift()) > > finally   maybeRaf() > >  function maybeRaf()   if (queuedActions.length && !frame)   frame = requestAnimationFrame(loop) > >  return action =>   if (!action.meta || !action.meta.raf)   return next(action) >  queuedActions.push(action) maybeRaf()  return function cancel()   queuedActions = queuedActions.filter(a => a !== action) > > >  /**  * Lets you dispatch promises in addition to actions.  * If the promise is resolved, its result will be dispatched as an action.  * The promise is returned from `dispatch` so the caller may handle rejection.  */ const vanillaPromise = store => next => action =>   if (typeof action.then !== 'function')   return next(action) >  return Promise.resolve(action).then(store.dispatch) >  /**  * Lets you dispatch special actions with a < promise >field.  *  * This middleware will turn them into a single action at the beginning,  * and a single success (or failure) action when the `promise` resolves.  *  * For convenience, `dispatch` will return the promise so the caller can wait.  */ const readyStatePromise = store => next => action =>   if (!action.promise)   return next(action) >  function makeAction(ready, data)   const newAction = Object.assign(>, action,  ready >, data) delete newAction.promise return newAction >  next(makeAction(false)) return action.promise.then( result => next(makeAction(true,  result >)), error => next(makeAction(true,  error >)) ) >  /**  * Lets you dispatch a function instead of an action.  * This function will receive `dispatch` and `getState` as arguments.  *  * Useful for early exits (conditions over `getState()`), as well  * as for async control flow (it can `dispatch()` something else).  *  * `dispatch` will return the return value of the dispatched function.  */ const thunk = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action)  // You can use all of them! (It doesn't mean you should.) const todoApp = combineReducers(reducers) const store = createStore(  todoApp, applyMiddleware(  rafScheduler,  timeoutScheduler,  thunk,  vanillaPromise,  readyStatePromise,  logger,  crashReporter ) ) 

Зачем нам нужны middleware redux?

Чем такой способ dispatch’а отличается от подобного, с использованием redux-thunk?

export const loginAction = (data) => async (dispatch) => < try < const res = await api.login(data); if(res.status < 300) < dispatch(setUserData(res.data)) >> catch(err) < alert(err) >> 

Есть ли какая-нибудь разница вообще? И если есть, то какой способ лучше использовать и почему? Почему redux настоятельно рекомендует использовать инструменты по типу middleware redux-thunk, ведь мои действия и так асинхронны, я делаю dispatch только тогда, когда мне пришел ответ от сервера?

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

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