Redux-Observable: изменить состояние и запустить последующее действие - PullRequest
1 голос
/ 08 апреля 2019

У меня есть следующий сценарий в наблюдаемой редукции.У меня есть компонент, который определяет, какой бэкэнд использовать, и должен установить бэкэнд-URL, используемый api-клиентом.И клиент, и URL-адрес содержатся в глобальном объекте состояния.

Порядок выполнения должен быть следующим: 1. проверить бэкэнд 2. при ошибке заменить URL-адрес бэкэнда, находящийся в состоянии 3. инициировать 3 действия для загрузки ресурсов с использованием новогоURL-адрес состояния бэкэнда

Что я делал до сих пор, так это на шаге 1. доступ к состоянию $ object из моего эпоса и изменение поддерживаемого URL-адреса.Кажется, это только половина работы.Состояние обновляется действиями, инициированными в 3. Все еще смотрите старое состояние и используете неправильный бэкэнд.

Каков стандартный способ обновления состояния между действиями, если вы зависите от порядка выполнения?

Мой API-Epic выглядит так:

export const authenticate = (action$, state$) => action$.pipe(
    ofType(actions.API_AUTHENTICATE),
    mergeMap(action =>
        from(state$.value.apiState.apiClient.authenticate(state$.value.apiState.bearer)).pipe(
            map(bearer => apiActions.authenticatedSuccess(bearer))
        )
    )
)

export const authenticatedSuccess = (action$, state$) => action$.pipe(
   ofType(actions.API_AUTHENTICATED_SUCCESS),
    concatMap(action => concat(
        of(resourceActions.doLoadAResource()),
        of(resourceActions.doLoadOtherResource()),
        of(resourceActions.doLoadSomethingElse()))
      )
)

1 Ответ

2 голосов
/ 10 апреля 2019

Обычный подход, который я нашел среди пользователей, обсуждающих на GitHub & StackOverflow, состоит в объединении нескольких эпопей, очень похожих на то, что, как я полагаю, ваш пример пытается продемонстрировать.Первая эпопея отправляет действие, когда оно «сделано».Редуктор прослушивает это действие и обновляет состояние магазина.Вторая эпопея (или много дополнительных эпопей, если вы хотите параллельные операции) прослушивает это же действие и запускает следующую последовательность рабочего процесса.Вторичные эпосы бегут за редукторами и видят обновленное состояние. Из документов :

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

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

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

export const workflow = (action$, state$) => action$.pipe(
  ofType(constants.START),
  withLatestFrom(state$),
  mergeMap(([action, state]) => // "state" is the value when the START action was dispatched
    concat(
      of(actions.increment()),
      state$.pipe(
        first(),
        map(state => // this new "state" is the _incremented_ value!
          actions.decrement()),
      ),
      defer(() => {
        const state = state$.value // this new "state" is now the _decremented_ value!
        return empty()
      }),
    ),
  ),
)

Существует множество способов получить текущее состояние из наблюдаемой!


Что касается следующегострока кода в вашем примере:

state$.value.apiState.apiClient.authenticate(state$.value.apiState.bearer)

Во-первых, передача API-клиента с использованием состояния не является распространенным / рекомендуемым шаблоном.Возможно, вы захотите взглянуть на внедрение API-клиента в качестве зависимости от ваших эпопей (это делает модульное тестирование намного проще!).Во-вторых, неясно, как клиент API получает текущий внутренний URL-адрес из состояния.Возможно ли, что клиент API использует кэшированную версию состояния?Если да, вы можете реорганизовать свой метод authenticate и передать текущий URL-адрес бэкэнда.

Вот пример, который обрабатывает ошибки и включает в себя вышеуказанное:

/**
 * Let's assume the state looks like the following:
 * state: {
 *   apiState: {
 *     backend: "URL",
 *     bearer: "token"
 * }
 */

// Note how the API client is injected as a dependency
export const authenticate = (action$, state$, { apiClient }) => action$.pipe(
  ofType(actions.API_AUTHENTICATE),
  withLatestFrom(state$),
  mergeMap(([action, state]) =>
    // Try to authenticate against the current backend URL
    from(apiClient.authenticate(state.apiState.backend, state.apiState.bearer)).pipe(
      // On success, dispatch an action to kick off the chained epic(s)
      map(bearer => apiActions.authenticatedSuccess(bearer)),
      // On failure, dispatch two actions:
      //   1) an action that replaces the backend URL in the state
      //   2) an action that restarts _this_ epic using the new/replaced backend URL
      catchError(error$ => of(apiActions.authenticatedFailed(), apiActions.authenticate()),
    ),
  ),
)

export const authenticatedSuccess = (action$, state$) => action$.pipe(
  ofType(actions.API_AUTHENTICATED_SUCCESS),
  ...
)

Кроме того, имейте в виду, когда цепочка эпопей, которая конструируется как concat , не будет ждать, пока цепочка эпопей "закончится".Например:

concat(
  of(resourceActions.doLoadAResource()),
  of(resourceActions.doLoadOtherResource()),
  of(resourceActions.doLoadSomethingElse()))
)

Если каждое из этих doLoadXXX действий "запускает" эпопею, все три, скорее всего, будут выполняться одновременно.Каждое действие будет отправлено одно за другим, и каждый эпос будет «запускаться» один за другим, не дожидаясь, пока предыдущий «закончится».Это потому, что эпопеи никогда по-настоящему не завершены .Они долгоживущие, бесконечные потоки.Вам нужно будет явно дождаться сигнала, который определяет, когда doLoadAResource завершится, если вы хотите, чтобы doLoadOtherResource запустил после doLoadAResource.

...