#8 ReduxJS - асинхронные екшены с помощью redux-thunk

poster
В этом уроке мы научимся писать асинхронные екшены с помощью миддлвары redux-thunk, а также узнаем, как настроить redux так, чтобы он работал одновременно с devtools и миддлварами.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

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

Самая простая и часто используемая библиотека для этого - это redux-thunk. Давайте установим ее командой

npm i redux-thunk --save

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

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

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

npm i redux-devtools-extension --save

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

import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

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

const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));

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

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

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

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

<div>
  <button onClick={this.props.onGetTracks}>Get tracks</button>
</div>

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

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

onGetTracks: () => {
  const asyncGetTracks = () => {
    return dispatch => {
      setTimeout(() => {
        console.log('I got tracks');
        dispatch({ type: 'FETCH_TRACKS_SUCCESS', payload: [] });
      }, 2000)
    }
  }
  dispatch(asyncGetTracks());
}

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

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

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

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

export const getTracks = () => {
  return dispatch => {
    setTimeout(() => {
      console.log('I got tracks');
      dispatch({ type: 'FETCH_TRACKS_SUCCESS', payload: [] });
    }, 2000)
  }
};

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

import { getTracks } from './actions/tracks';

onGetTracks: () => {
  dispatch(getTracks());
}

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

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

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

export const getTracks = () => dispatch => {
  setTimeout(() => {
    console.log('I got tracks');
    dispatch({ type: 'FETCH_TRACKS_SUCCESS', payload: [] });
  }, 2000)
};

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

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

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

var mockApiData = [
  {
    id: 1,
    name: 'Enter Sandman'
  },
  {
    id: 2,
    name: 'Welcome Home'
  },
  {
    id: 3,
    name: 'Master of Puppets'
  },
  {
    id: 4,
    name: 'Fade to Black'
  },
  {
    id: 5,
    name: 'Nothing Else Matters'
  }
];

export const getTracks = () => dispatch => {
  setTimeout(() => {
    console.log('I got tracks');
    dispatch({ type: 'FETCH_TRACKS_SUCCESS', payload: mockApiData });
  }, 2000)
};

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

const initialState = [];

export default function tracks(state = initialState, action) {
  if (action.type === 'ADD_TRACK') {
    return [
      ...state,
      action.payload
    ];
  } else if (action.type === 'FETCH_TRACKS_SUCCESS') {
    return action.payload;
  }
  return state;
}

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

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

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Nastya
3 месяцев назад
Спасибо за уроки) Очень подробное объяснение, а главное примеры хорошие.
monsterlessons
3 месяцев назад
На здоровье)
Алексей Якубук
3 месяцев назад
Автор, большое тебе спасибо, все просто , понятно, и ничего лишнего
monsterlessons
3 месяцев назад
На здоровье)
Денис Лебединский
6 месяцев назад
Для чего задавать вызов функции через 2сек? я попробовал без задержки прекрасно работает
monsterlessons
6 месяцев назад
Задержку делают чтобы показать что ответ приходит не сразу, как и от API. В реальном коде делать это не нужно.
Денис Лебединский
6 месяцев назад
я потратил много времени на то что бы понять как задипатчить данные от сервера. у меня получился такой код. (обработки ошибок не стал писать для читаемости) export const onGetHouse = () =>dispatch=> { setTimeout(() => { fetch('/house') .then(res => res.json()) .then(list => dispatch({ type: 'FETCH_TRACKS_SUCCESS', house:list.house })); }, 2000) };
Денис Лебединский
6 месяцев назад
урок очень полезный)
monsterlessons
6 месяцев назад
Вы все правильно написали.
Krot TV
1год назад назад
Доброе утро. Я никак не могу понять в чем смысл redux-thunk. Я пробывал использовать dispatch в setTimeout без redux-thunk и вроде результат такой же, dispatch выстреливает через 2 секунды. Вот более реальный пример... Если, у меня есть fetch запрос на сервер и я в then вызову dispatch по идее он будет асинхронным, так как он выстрелит только после того как придут данные...Вообщем я запутался... А доки Redux, реально очень запутанные.
monsterlessons
1год назад назад
Добрый день. redux-thunk по факту и используется практически всегда для асинхронных запросов. Именно так, как вы написали. В экшене мы пишем fetch и диспатчим в then полученные данные. Просто без redux-thunk нельзя в экшенах возвращать функцию. Для этого он нам и нужен.
Krot TV
1год назад назад
О, теперь я все понял. Спасибо !
talnax
1год назад назад
Отлично, спасибо
talnax
1год назад назад
Здравствуйте, пытаюсь понять стрелочную запись кода, который Вы привели в уроке, переписав его на старый манер но не получается. Как переписать этот код используя function()... export const getTracks = () => { return dispatch => { setTimeout(() => { console.log('I got tracks'); dispatch({ type: 'FETCH_TRACKS_SUCCESS', payload: [] }); }, 2000) } };
monsterlessons
1год назад назад
Добрый день. Просто замените все стрелки словом function. export const getTracks = function () { return function (dispatch) { setTimeout(function () { console.log('I got tracks'); dispatch({ type: 'FETCH_TRACKS_SUCCESS', payload: [] }); }, 2000) } }
Alex Werst
1год назад назад
Здравствуйте! Вы не могли бы создать урок, где объяснили бы работу с асинхронными екшенами более детально? Документация Redux как-то больше запутывает, чем объясняет
monsterlessons
1год назад назад
Добрый день. Не планируется, так как на данным момент много других уже начатых серий. Этот урок показывает базовый пример использования и я надеялся, что его хватит для понимания. Если же вы хотите увидеть, как они используются в реальном проекте, то я могу вам порекомендовать курс "Интернет-магазин на React/Redux", но он платный. https://monsterlessons.com/project/series/internet-magazin-na-reactredux
Alex Werst
1год назад назад
Ок, спасибо! Сам урок-то понятен. Курс уже куплен - отлично получилось
Karen Kostanyan
2 лет назад
какова была роль thunk-a ??? и как будет связка апи через xmlhttprequest?
Karen Kostanyan
2 лет назад
это для того чтобы смогли в dispatch отправить функцию как параметр?
monsterlessons
2 лет назад
Именно. И внутри функции можно писать любой асинхронный код.
alf
2 лет назад
Возможно я чегото не понимаю, но зачем нужно "замыкание"? Лишняя обертка которая не используется, нельзя ли вместо вызова экшена dispatch(getTracks()) который возвращает ф-цию для диспатча, просто передать саму эту ф-цию: dispatch(getTracks) .... export const getTracks = dispatch => { setTimeout() }
monsterlessons
2 лет назад
Вы вполне можете это делать. Вас redux никак не ограничивает. Но redux-thunk был придуман, чтобы екшены, которые мы используем были максимально чистыми, поэтому с его помощью мы можем передавать в dispatch функцию, которая возвращает функцию. Если в каждый екшен мы будем прокидывать dispatch, то код будет сложнее поддерживать. Есть очень хорошее обьяснение на SO, http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559