Зачем нам промежуточное ПО для асинхронного потока в Redux?

согласно документам, "без промежуточного по, Redux store поддерживает только синхронный поток данных". Я не понимаю, почему это так. Почему компонент контейнера не может вызвать асинхронный API, а затем dispatch действия?

например, представьте себе простой интерфейс: поле и кнопка. Когда пользователь нажимает кнопку, поле заполняется данными с удаленного сервера.

A field and a button

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

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

Примечание

7 ответов


что не так с этим подходом? Почему я хочу использовать Redux Thunk или Redux Promise, как предполагает документация?

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

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

Middleware как Redux Thunk или Redux Promise просто дает вам "синтаксический сахар" для отправки thunks или обещаний, но вы не обязательно использовать его.

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

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

но с thunk Middleware вы можете написать это так:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

так что нет большой разницы. Одна вещь, которая мне нравится в последнем подходе, заключается в том, что компоненту все равно, что создатель действия асинхронен. Он просто зовет dispatch нормально, оно может также использовать mapDispatchToProps чтобы связать такой творец с синтаксисом. Компоненты не знают, как реализуются создатели действий, и вы можете переключаться между различными асинхронными подходами (Redux Thunk, Redux Promise, Redux Saga) без изменения компонентов. С другой стороны, при первом явном подходе ваши компоненты знают ровно что конкретный вызов является асинхронным и нуждается dispatch для передачи по некоторому соглашению (например, в качестве параметра синхронизации).

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

С первым подходом мы нужно помнить о том, какого рода действие Творца мы называем:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

С Redux Thunk создатели действий могут dispatch результат других создателей действий и даже не думаю, являются ли они синхронными или асинхронными:

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

при таком подходе, если вы позже хотите, чтобы ваши создатели действий заглянули в текущее состояние Redux, вы можете просто использовать второй getState аргумент передается thunks без изменения вызывающего кода на все:

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

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

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

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

наконец, Redux Thunk и друзья-это всего лишь один из возможных подходов к асинхронным запросам в приложениях Redux. Еще один интересный подход Redux Saga что позволяет определить долгосрочные демоны ("саги"), которые принимают действия по мере их поступления и преобразуют или выполняют запросы перед выводом действий. Это перемещает логику от создателей действий в саги. Возможно, вы захотите проверить это, а позже выбрать то, что вам подходит наиболее.

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

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


нет.

но... вы должны использовать redux-saga:)

ответ Дана Абрамова прав насчет redux-thunk но я расскажу немного больше о redux-saga это очень похоже, но более мощным.

императив против декларативного

  • дом: jQuery является императивом / React является декларативным
  • монады: IO является императивным / свободным декларативный
  • Redux эффекты: redux-thunk важно / redux-saga декларативная

когда у вас есть удар в руках, как монада Ио или обещание, вы не можете легко знать, что он будет делать, как только вы выполните. Единственный способ проверить thunk-выполнить его и издеваться над диспетчером (или всем внешним миром, если он взаимодействует с большим количеством вещей...).

если вы используете насмешки, то вы не делаете функциональное программирование.

увиденный через объектив побочных эффектов, насмешки-это флаг, что ваш код нечист, и в глазах функционального программиста доказательство того, что что-то не так. Вместо того, чтобы загружать библиотеку, чтобы помочь нам проверить айсберг нетронутым, мы должны плыть вокруг него. Хардкорный парень TDD/Java однажды спросил меня, как вы издеваетесь в Clojure. Ответ: мы обычно не. Мы обычно видим его как знак, что нам нужно изменить наши код.

источник

саги (как они были реализованы в redux-saga) являются декларативными и, как и компоненты Free monad или React, их намного проще тестировать без каких-либо макетов.

см. также статьи:

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

(на самом деле, Redux-saga похожа на гибрид: поток императивен, но эффекты декларативны)

путаница: действия / события / команды...

существует много путаницы в интерфейсном мире о том, как могут быть связаны некоторые бэкэнд-концепции, такие как CQRS / EventSourcing и Flux / Redux, в основном потому, что в Flux мы используем термин "действие", который иногда может представлять собой как императивный код (LOAD_USER) и событий (USER_LOADED). Я считаю, что как event-sourcing, вы должны только отправлять события.

использование саг на практике

представьте себе приложение со ссылкой на профиль пользователя. Идиоматический способ справиться с этим с обоими промежуточными программами будет:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>

function loadUserProfile(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
      err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
    );
}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>


function* loadUserProfileOnNameClick() {
  yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}

function* fetchUser(action) {
  try {
    const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
    yield put({ type: 'USER_PROFILE_LOADED', userProfile })
  } 
  catch(err) {
    yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
  }
}

эта сага переводится как:

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

как вы можете видеть, есть некоторые преимущества redux-saga.

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

вы держите создателей действий чистыми. Обратите внимание, что это по-прежнему полезно держать actionCreators (в сагах put и компоненты dispatch), поскольку это может помочь вам добавить проверку действий (утверждения/поток/typescript) в будущем.

ваш код становится гораздо более проверяемым, поскольку эффекты декларативны

вам больше не нужно запускать RPC-подобные вызовы, такие как actions.loadUser(). Ваш пользовательский интерфейс просто должен отправить то, что произошло. Мы только стреляем!--37-->событий (всегда в прошедшем времени! и больше никаких действий. Это означает, что вы можете создать развязано "уток" или Ограниченные Контексты и что сага может действовать как точка соединения между этими модульными компонентами.

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

например, представьте бесконечный вид прокрутки. CONTAINER_SCROLLED может привести к NEXT_PAGE_LOADED, но это действительно ответственность прокручиваемый контейнер, чтобы решить, следует ли загружать другую страницу? Тогда он должен знать о более сложных вещах, таких как была ли успешно загружена последняя страница или если уже есть страница, которая пытается загрузить, или если больше не осталось элементов для загрузки? Я так не думаю: для максимального повторного использования прокручиваемый контейнер должен просто описать, что он был прокручен. Загрузка страницы - это "бизнес-эффект" этого свитка

некоторые могут возразить, что генераторы могут по своей сути скрывать состояние вне redux store с локальными переменными, но если вы начнете организовывать сложные вещи внутри thunks, запустив таймеры и т. д., У вас все равно будет такая же проблема. И есть select эффект, который теперь позволяет получить некоторое состояние из вашего магазина Redux.

саги могут быть перемещены во времени, а также позволяет сложные протоколирования и dev-инструменты, которые в настоящее время работают. Вот некоторые простые асинхронные протоколирования, которые уже реализовано:

saga flow logging

развязка

саги не только заменяют redux thunks. Они приходят от backend / распределенных систем / event-sourcing.

это очень распространенное заблуждение, что саги просто здесь, чтобы заменить ваши redux thunks с лучшей тестируемостью. На самом деле это просто деталь реализации redux-saga. Использование декларативных эффектов лучше, чем thunks для тестируемости, но шаблон saga может быть реализован поверх императивного или декларативного кода.

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

чтобы упростить это для frontend world, представьте, что есть widget1 и widget2. Когда нажимается какая-то кнопка на widget1, она должна влиять на widget2. Вместо того, чтобы муфта 2 виджеты вместе (т. е. widget1 отправляет действие, которое нацелено на widget2), widget1 отправляет только то, что его кнопка была нажата. Затем сага прослушивает эту кнопку, а затем обновляет widget2, удаляя новое событие, о котором известно widget2.

это добавляет уровень косвенности, который не нужен для простых приложений, но упрощает масштабирование сложных приложений. Теперь вы можете публиковать widget1 и widget2 в разных репозиториях npm, чтобы они никогда не знали о каждом другие, не имея их для совместного использования глобального реестра действий. 2 виджета теперь ограничены контекстами, которые могут жить отдельно. Они не нуждаются друг в друге, чтобы быть последовательными и могут быть повторно использованы в других приложениях, а также. Сага является точкой соединения между двумя виджетами, которые координируют их значимым способом для вашего бизнеса.

некоторые хорошие статьи о том, как структурировать приложение Redux, на котором вы можете использовать Redux-saga для развязки причины:

конкретный usecase: система оповещения

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

я не хочу, чтобы мои компоненты JSX решали, когда уведомление будет отображаться/скрываться. Я просто даю ему возможность запросить уведомление и оставить сложные правила внутри саги. Такого рода вещи довольно трудно реализовать с помощью thunks или обещает выделить.

notifications

я описал здесь как это можно сделать с помощью saga

почему это называется Сага?

термин сага происходит от backend мира. Сначала я познакомил Ясина (автора Redux-saga) с этим термином в долгая дискуссия.

изначально, этот термин был введен с статьи, шаблон саги должен был использоваться для обработайте возможную согласованность в распределенных транзакциях, но ее использование было расширено до более широкого определения бэкэнд-разработчиками, так что теперь оно также охватывает шаблон "process manager" (каким-то образом исходный шаблон saga является специализированной формой process manager).

сегодня термин "сага" сбивает с толку, поскольку он может описать 2 разные вещи. Поскольку он используется в redux-saga, он не описывает способ обработки распределенных транзакций, а скорее способ координации действий в ваше приложение. redux-saga также можно было бы назвать redux-process-manager.

Читайте также:

варианты

Если вам не нравится идея использования генераторы, но вас интересует шаблон саги и его свойства развязки, вы также можете достичь того же с redux-observable, который использует имя epic чтобы описать тот же самый шаблон, но с RxJS. Если вы уже знакомы с Rx, вы будете чувствовать себя как дома.

const loadUserProfileOnNameClickEpic = action$ =>
  action$.ofType('USER_NAME_CLICKED')
    .switchMap(action =>
      Observable.ajax(`http://data.com/${action.payload.userId}`)
        .map(userProfile => ({
          type: 'USER_PROFILE_LOADED',
          userProfile
        }))
        .catch(err => Observable.of({
          type: 'USER_PROFILE_LOAD_FAILED',
          err
        }))
    );

некоторые полезные ресурсы redux-saga

2017 советует

  • не злоупотребляйте Redux-saga только ради его использования. Тестируемые вызовы API не стоят того.
  • не удаляйте thunks из вашего проекта для большинства простых случаев.
  • не стесняйтесь отправлять удары в yield put(someActionThunk) если обретать смысл.

если вы боитесь использовать Redux-saga (или Redux-observable), но просто нужен шаблон развязки, проверьте redux-отправка-подписаться: он позволяет прослушивать депеши и запускать новые депеши в прослушивателе.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});

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

у меня была очень похожая мысль при работе над новым проектом, мы только начали свою работу. Я был большим поклонником элегантной системы vanilla Redux для обновления магазина и переориентации компонентов таким образом, чтобы оставаться вне кишок дерева компонентов React. Мне показалось странным цепляться за это элегантное dispatch механизм для обработки асинхронность.

Я закончил тем, что пошел с действительно похожим подходом к тому, что у вас есть в библиотеке, которую я учитывал из нашего проекта, который мы назвали react-redux-контроллер.

Я закончил тем, что не пошел с точным подходом, который у вас есть выше по нескольким причинам:

  1. как вы это написали, эти диспетчерские функции не имеют доступа к магазину. Вы можете несколько обойти это, имея ваши компоненты UI проходят во всех информация, необходимая диспетчерской функции. Но я бы сказал, что это связывает эти компоненты пользовательского интерфейса с логикой диспетчеризации без необходимости. И что более проблематично, нет очевидного способа для диспетчерской функции получить доступ к обновленному состоянию в асинхронных продолжениях.
  2. диспетчерские функции имеют доступ к dispatch себя через лексическую область. Это ограничивает возможности рефакторинга после того, как connect заявление выходит из-под контроля-и это выглядит довольно громоздким только с этим update метод. Поэтому вам нужна система, позволяющая вам составлять эти функции диспетчера, если вы разбиваете их на отдельные модули.

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

  • redux-thunk делает это функциональным способом, передавая их в ваши удары (что делает их не совсем ударами вообще, по определениям купола). Я не работал с другими dispatch middleware подходы, но я предполагаю, что они в основном одинаковы.
  • react-redux-controller делает это с помощью корутина. В качестве бонуса он также дает вам доступ к" селекторам", которые являются функциями, которые вы, возможно, передали в качестве первого аргумента connect, вместо того, чтобы работать непосредственно с сырьем, нормализуется магазине.
  • вы также может сделайте это объектно-ориентированным способом, впрыснув их в this контекст, с помощью различных возможных механизмов.

обновление

мне приходит в голову, что часть этой головоломки является ограничением react-redux. Первый аргумент connect получает снимок состояния, но не отправка. Второй аргумент получает dispatch, но не государство. Ни один аргумент не получает thunk, который закрывается над текущим состоянием, для возможность видеть обновленное состояние во время продолжения/обратного вызова.


цель Абрамова-и всех в идеале-просто инкапсулировать сложность (и асинхронные вызовы) в том месте, где это наиболее уместно.

где лучшее место для этого в стандартном потоке данных Redux? Как насчет:

  • редуктор? Ни за что. Они должны быть чистыми функциями без побочных эффектов. Обновление магазина-дело серьезное, сложное. Не загрязняйте его.
  • Стремно Смотреть Компоненты? Наверняка Нет. У них есть одна проблема: презентация и взаимодействие с пользователем, и они должны быть максимально простыми.
  • Компоненты Контейнера? возможно, но неоптимально. Это имеет смысл в том, что контейнер-это место, где мы инкапсулируем некоторую сложность, связанную с представлением, и взаимодействуем с хранилищем, но:
    • контейнеры должны быть более сложными, чем тупые компоненты, но это все еще одна ответственность: обеспечение Привязок между view и государство/магазин. Ваша асинхронная логика-это отдельная проблема.
    • поместив его в контейнер, вы будете блокировать свою асинхронную логику в одном контексте для одного вида/маршрута. Плохая идея. В идеале все это многоразово и полностью развязано.
  • SOME другой сервисный модуль? плохая идея: вам нужно будет ввести доступ к магазину, что является кошмаром ремонтопригодности/тестируемости. Лучше пойти с зерном Redux и получить доступ к магазину ТОЛЬКО используя предоставленные API / модели.
  • действия и промежуточные программы, которые их интерпретируют? почему бы и нет?! Для начала, это единственный большой вариант, который у нас остался. :- ) Более логично, что система действий-это развязанная логика выполнения, которую вы можете использовать из любого места. Он имеет доступ к магазину и может отправлять больше действий. Он имеет одну ответственность, которая заключается в организации потока управления и данных приложения, и большинство асинхронных вписывается в что.
    • как насчет создателей действий? Почему бы просто не сделать асинхронность там, а не в самих действиях и в промежуточном по?
      • во-первых и самое главное, у создателей нет доступа к магазину, как это делает middleware. Это означает, что вы не можете отправлять новые условные действия, не можете читать из магазина, чтобы составить асинхронный и т. д.
      • так, сложности в комплексе необходимости, и сохранить все остальное просто. Создатели могут быть простой, относительно чистые функции, которые легко проверить.

чтобы ответить на вопрос, который задал в начале:

почему компонент контейнера не может вызвать асинхронный API, а затем отправить действия?

имейте в виду, что эти документы предназначены для Redux, а не Redux plus React. Redux в магазинах подключено к компонентам React может делать именно то, что вы говорите, но простой магазин Jane Redux без промежуточного программного обеспечения не принимает аргументы dispatch за исключением простого ol' объекты.

без middleware, можно конечно еще сделать

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

но это аналогичный случай, когда асинхронность обернута вокруг Redux, а не обрабатывается by "возвращение". Таким образом, промежуточное ПО позволяет асинхронность путем изменения того, что может быть передано непосредственно в dispatch.


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

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

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

если вы также хотите поддержать обещания, параметры, саг или сумасшедший таможни и весьма декларативный создатели действий, затем Redux может сделать это, просто изменив то, что вы передаете dispatch (ака, что вы возвращаете от создателей действий). Отсутствие mucking с компонентами React (или connect звонков) обязательно.


хорошо, давайте начнем видеть, как middleware работает во-первых, что вполне ответить на вопрос, это исходный код applyMiddleWare функция в Redux:

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

посмотрите на эту часть, посмотрите, как наши отправка стать функции.

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • обратите внимание, что каждому промежуточному ПО будет дано dispatch и getState функции как именованные аргументы.

хорошо, вот как Redux-thunk как один из наиболее часто используемых middlewares для Redux представить себя:

Redux Thunk middleware позволяет писать создателей действий, которые возвращаются функция вместо действия. Преобразователь может быть использован для задержки отправка действия или отправка только при определенном условии встретились. Внутренняя функция получает методы хранения dispatch и getState в качестве параметров.

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

так что же такое thunk? Вот как это вводится в Википедии:

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

термин возник как шутливое производное от "думать".

thunk-это функция, которая обертывает выражение для задержки его оценка.

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

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

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

Apply middleware Redux


использовать Redux-saga-лучшее промежуточное ПО в реализации React-redux.

Ex: магазин.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

а потом сага.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

и затем действие.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

и потом редуктор.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

и затем main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));

попробуйте это.. работает