thunk.md 6.2 KB

React-Thunk

Поток данных.

В целом в React-Redux поток данных несложен:

  • Компонент через connect присоединяется через props к redux, получая actionCreator-ы для отправки в redux, и данные из хранилища.
  • При выполнении диспетчеризации action меняется состояние хралища.
  • Новое состояние автоматически обновляет все подписанные через connect компоненты.

В этом цикле не совсем ясно, куда воткнуть обращения к бэку, при условии, что:

  • Компонент должен остаться независимым и связанным с внешним миром только через connect,
  • Redux - хранилище синхронное, а получение информации с бэка - процесс многоэтапный и асинхронный.
  • Компоненты должны знать о состоянии процесса общения с бэком (состоянии промиса, как минимум).

В таком случае слой отправки-получения данных можно встроить в два места:

  • В actionCreator, перед диспетчеризацией.
  • В том или ином слушателе состояния redux (используя subscribe). Внутри хранилища создается специальная ветвь для хранения информации о желании компонентов получить что-то с back-end.

Первый подход более прост и распространен.

actionCreator

В коде 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

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))
        )
    }
}
  • actionCreator возвращает не объект, а функцию;
  • Redux её запускает, передавая свой dispatch;
  • функция инициирует асинхронный процесс и использует простые объектные actionCreator для оповещении хранилища;

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 мало отличается от асинхронной функции; то при нескольких обращениях зависящих друг от друга, код асинхронной функции будет намного прозрачнее.