Redux-Observable:修改状态并触发后续操作

时间:2019-04-08 08:30:08

标签: javascript reactjs react-redux rxjs redux-observable

我在redux-observable中有以下情况。我有一个组件,可以检测要使用的后端,并应设置api-client使用的后端URL。客户端和URL都保存在全局状态对象中。

执行顺序应为: 1.检查后端 2.错误时替换后端网址 3.触发3个操作,以使用新的后端状态URL加载资源

到目前为止,我所做的是,在第1步中。从史诗内部访问state $对象并修改支持的URL。这似乎只有一半的工作。通过3中触发的操作来更新状态。仍然可以看到旧状态并使用错误的后端。

如果取决于执行顺序,在两次动作之间更新状态的标准方法是什么?

我的API史诗如下:

rume

1 个答案:

答案 0 :(得分:2)

我发现用户在GitHub和StackOverflow上讨论的一种常见方法是链接多个史诗,就像我相信您的示例试图演示的那样。第一个史诗在“完成”时调度一个动作。减速器侦听此操作并更新商店的状态。第二个史诗(或多个其他史诗,如果要进行并发操作)侦听此相同操作并开始工作流程的下一个序列。次要史诗在减速器之后运行,因此可以看到更新状态。 From the docs

  

史诗与常规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客户端不是常见/推荐的模式。您可能需要查看史诗中的injecting the API client as a dependency(这使单元测试变得更加容易!)。其次,目前尚不清楚API客户端如何从状态获取 current 后端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完成时间的信号。