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