В целом в React-Redux поток данных несложен:
connect
присоединяется через props
к redux, получая actionCreator
-ы для отправки в redux, и данные из хранилища.connect
компоненты.В этом цикле не совсем ясно, куда воткнуть обращения к бэку, при условии, что:
connect
,В таком случае слой отправки-получения данных можно встроить в два места:
actionCreator
, перед диспетчеризацией.subscribe
). Внутри хранилища создается специальная ветвь для хранения информации о желании компонентов получить что-то с back-end.Первый подход более прост и распространен.
В коде actionCreator должны быть реализованы асинхронные обращения. Если разобрать жизненный путь Promise
, то обычный actionCreator будет представлять что-то вроде:
//три состояния
const actionPending = () => ({ type: 'SET_STATUS', status: 'PENDING', payload: null, error: null })
const actionResolved = payload => ({ type: 'SET_STATUS', status: 'RESOLVED', payload, error: null })
const actionRejected = error => ({ type: 'SET_STATUS', status: 'REJECTED', payload: null, error })
//асинхронный многоэтапный экшен
const actionFetch = () => {
let promise = fetch("...")
promise.then(
data => store.dispatch(actionResolved(data)),
error => store.dispatch(actionRejected(error))
)
return actionPending()
}
Таким образом синхронная функция actionFetch
обращается куда надо на сервер, навешивает обработчики результатов промиса в then
, и возвращает
изначальное состояние actionPending
.
Когда промис поменяет свое состояние, это будет обработано redux через действия actionResolved
и actionRejected
.
Где-то в недрах редьюсеров:
...
if (action.type === 'SET_STATUS'){
return {status: action.status, payload: action.payload, error: action.error}
}
...
Что в этом подходе неудобно, так это привязка к store
, и то, что actionFetch
"мимикрирует" под actionPending
.
Встречайте, redux-thunk
Redux-thunk предоставляет возможность передавать действие-функцию вместо действия-объекта . Теперь actionCreator
может возвращать функции, и dispatch
их запустит. Сама же функция может реализовывать любой длительный асинхронный
процесс, запуская dispatch
для изменения состояния Redux.
npm install --save redux-thunk
import thunk from 'redux-thunk';
...
const store = createStore(msgStatusReducer, applyMiddleware(thunk)) //вторым параметром идет миддлварь
Теперь функция actionFetch
выглядит немного по-другому:
const actionFetch = () => {
return dispatch => { //возвращаем функцию.
let promise = fetch("...")
dispatch(actionPending())
promise.then(
data => dispatch(actionResolved(data)),
error => dispatch(actionRejected(error))
)
}
}
dispatch
;async function
тоже можно:function actionFetch(){
return async function (dispatch){
dispatch(actionPending())
try {
dispatch(actionResolved(await fetch(...)))
}
catch (e) {
dispatch(actionRejected(e))
}
}
}
Подход с использованием async function
предпочтителен, так как он позволяет линейно описать асинхронный процесс любой сложности. Подобным образом работает Redux-Saga.
И если на простых примерах синхронный подход с then
мало отличается от асинхронной функции; то при нескольких обращениях зависящих друг от друга, код асинхронной
функции будет намного прозрачнее.