Зачем нам использовать промежуточное ПО для async-потока в Redux?

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

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

Поле и кнопка

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>;
    }
}

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

Обратите внимание на функцию update в вызове connect. Он отправляет действие, которое сообщает App, что оно обновляется, а затем выполняет асинхронный вызов. После завершения вызова предоставленное значение отправляется как полезная нагрузка другого действия.

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

EDIT: Я искал репозиторий Redux для подсказок и обнаружил, что Action Creators должны были быть чистыми функциями в прошлом. Например, здесь пользователь пытается лучше объяснить поток асинхронных данных:

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

Создателям действий больше не требуется быть чистыми. Таким образом, в прошлом было обязательно требовалось промежуточное ПО, которое, вероятно, требовалось, но кажется, что это это уже не так?

Ответ 1

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

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

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

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

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

// 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 async
    loadOtherData(dispatch, userId) // pass dispatch first: it async
  );
}


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

С помощью создателей Action 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 Thunk или Redux Promise, заключается в том, что компоненты arent знают о том, как создатели действия реализованы, и заботятся ли они о состоянии Redux, являются ли они синхронными или асинхронными и не требуют ли они других действий создатели. Недостаток - это немного косвенное отношение, но мы считаем его достойным в реальных приложениях.

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

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

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

Ответ 2

У вас нет.

Но... вы должны использовать саму саму саму:)

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

Императивный VS декларативный

  • DOM: jQuery является обязательным /React является декларативным
  • Монады: IO является обязательным /Free является декларативным
  • Эффекты Redux: redux-thunk обязательно / redux-saga является декларативным

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

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

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

Источник

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

См. также article:

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

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

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

В интерфейсном мире есть много путаницы в отношении того, как некоторые базовые концепции, такие как 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 позволяет выразить, что вы заинтересованы только в том, чтобы получить данные последнего имени пользователя (обработайте concurrency проблемы в случае, если пользователь очень быстро нажимает на многие имена пользователей). Такие вещи тяжело с грохотом. Вы могли бы использовать takeEvery, если вы не хотите этого поведения.

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

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

Вам больше не нужно запускать вызовы типа rpc типа actions.loadUser(). Ваш пользовательский интерфейс должен просто отправить то, что ПРОИСХОДИТ. Мы стреляем только события (всегда в прошедшем времени!), А не действия. Это означает, что вы можете создавать разделенные "утки" или Ограниченные контексты и что сага может выступать в качестве точки соединения между этими модульными компонентами.

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

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

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

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

saga flow logging

Развязка

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

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

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

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

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

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

Конкретная usecase: система уведомлений

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

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

уведомления

Я описал здесь, как это можно сделать с помощью саги

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

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

Сегодня термин "сага" запутан, поскольку он может описывать две разные вещи. Поскольку он используется в redux-саге, он не описывает способ обработки распределенных транзакций, а скорее способ координации действий в вашем приложении. 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
        }))
    );

Некоторые вспомогательные ресурсы для редукции-саги

2017 советует

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

Если вы испугались использования Redux-saga (или Redux-наблюдаемого), но просто нужно развязать шаблон, проверьте redux-dispatch-subscribe: it позволяет прослушивать рассылки и запускать новые рассылки в слушателе.

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

Ответ 3

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

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

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

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

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

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

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

Обновление

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Без промежуточного программного обеспечения вы, конечно же, могли бы

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

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


Тем не менее, дух вашего предложения, я думаю, действителен. Есть, конечно, другие способы обработки асинхронности в приложении Redux + React.

Одним из преимуществ использования промежуточного программного обеспечения является то, что вы можете продолжать использовать создателей действий как обычно, не беспокоясь о том, как именно они подключены. Например, используя 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 является (или должен быть) асинхронным.

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

Ответ 6

Хорошо, давайте сначала посмотрим, как работает промежуточное ПО, что вполне отвечает на вопрос, это исходный код функции pplyMiddleWare в 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 как одно из наиболее часто используемых промежуточных программ для Redux представляет:

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

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

Так что, черт возьми, такое? Вот как это было введено в Википедии:

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

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

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

Ответ 7

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

Пример:    store.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;

А потом saga.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()
       ]
    }

А потом action.js

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

А затем Reducer.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'));

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

Ответ 8

Есть создатели синхронных действий, а затем есть создатели асинхронных действий.

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

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

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

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

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

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

В контексте промежуточного программного обеспечения с избыточностью, такого как redux-thunk, промежуточное программное обеспечение помогает нам иметь дело с создателями асинхронных действий, так как это то, что Redux не может обработать "из коробки".

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

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

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

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

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

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

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

Ответ 9

Ответить на вопрос:

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

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

Первая причина - это разделение интересов: задача action creator не вызывать api и возвращать данные, вам нужно передать два аргумента вашим action creator function, action type и payload.

Вторая причина в том, что redux store ожидает простой объект с обязательным типом действия и, необязательно, payload (но здесь вы также должны передать полезную нагрузку).

Создатель действия должен быть простым объектом, как показано ниже:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

И от Redux-Thunk midleware до dispache результат вашего api call до соответствующего action.