Подтвердить что ты не робот

Как запускать вызовы AJAX в ответ на изменения состояния с помощью Redux?

Я конвертирую существующую модель состояния в Redux, и по большей части она была безболезненной. Однако в одном случае у меня возникают проблемы с преобразованием "наблюдаемых" запросов состояния ajax. По сути, у меня есть определенные запросы ajax, "связанные" с другими частями штата, поэтому независимо от того, кто их модифицирует, они всегда будут выдаваться правильно. Я получаю подобное поведение, подписываясь на обновления магазина Redux, но стрельба в слушателе выглядит как хак.

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

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

Есть ли общепринятые правила, связанные с этим? Это не простое приложение, в котором ad hoc-запросы ajax выполняются по мере взаимодействия компонентов, большинство данных распределяется между несколькими компонентами, а запросы оптимизируются и извлекаются в ответ на изменение состояния.

TL;DR; Я хочу запускать ajax-запросы в ответ на изменения состояния, а не когда происходит определенное действие. Есть ли лучший, "специфичный" для Redux способ организации action/actionCreators, чтобы издеваться над этим поведением, за исключением того, что они запускают эти действия в слушателе подписки?

4b9b3361

Ответ 1

Использование store.subscribe()

Самый простой способ - просто использовать метод store.subscribe():

let prevState
store.subscribe(() => {
  let state = store.getState()

  if (state.something !== prevState.something) {
    store.dispatch(something())
  }

  prevState = state
})

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

Использование цикла Redux

Вы можете посмотреть Redux Loop, который позволяет описывать эффекты (такие как AJAX), вызывает вместе с обновлениями состояния в ваших редукторах.

Таким образом, вы можете "вернуть" эти эффекты в ответ на определенные действия, как сейчас, в настоящее время return следующее состояние:

export default function reducer(state, action) {
  switch (action.type) {
    case 'LOADING_START':
      return loop(
        { ...state, loading: true },
        Effects.promise(fetchDetails, action.payload.id)
      );

    case 'LOADING_SUCCESS':
      return {
        ...state,
        loading: false,
        details: action.payload
      };

Этот подход вдохновлен Elm Architecture.

Использование Redux Saga

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

function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED",message: e.message});
   }
}

function* mySaga() {
  yield* takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

Нет истинного пути

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

Ответ 2

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

Скажем, у меня есть локальное действие:

const updateField = (val) => {
  {type: UPDATE_FIELD, val}
}

И поле ввода с:

<input type='text' onChange={this.props.updateField.bind(this.val)}>

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

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

const updateFieldOnServer = (val) => {
  return (dispatch) => {
    MAKE_AJAX.done(
      FIRE_SOME_ACTIONS_ON_SUCCESS
    ).failure(
      FIRE_SOME_ACTIONS_ON_FAILURE
    )
  }
} 

Это просто простое асинхронное действие thunk, которое каким-то образом делает запрос ajax, возвращает promises и делает что-то еще при успехе или неудаче.

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

import _ from 'lodash'
import {
  UPDATE_FIELD,
} from 'actionsPath'

export default ({ dispatch }) => next => action => {
  if(_.includes([UPDATE_FIELD], action.type)){
    switch(action.type){
      case UPDATE_FIELD:
        dispatch(updateFieldOnServer(action.val))
    }
  }
  return next(action)
}

Я могу добавить его в свой стек:

import ServerUpdatesMiddleware from 'pathToMe'

const createStoreWithMiddleware = applyMiddleware(
  ServerUpdatesMiddleware,
  thunkMiddleware,
  logger
)(createStore);

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Но catch здесь - объект состояния, для которого вы проверяете, должен быть добавлен как компонентный реквизит через mapStateToProps, так что он будет передан компоненту componentWillReceiveProps. Надеюсь, это поможет!