使用Redux-observable时,如何通过单个操作和单个流,如何基于过滤器调度不同的操作

时间:2018-06-30 19:41:42

标签: redux rxjs observable redux-observable

更新

这是一个使用redux-observable的工作示例。 https://redux-observable-playground-ykzsyp.stackblitz.io可以使用mergeMap和if / else语句实现我想要的功能,但是我希望使用Observable.filter,因为这看起来更优雅。


原始问题

我有一个史诗,当前正在调度一个动作,但是想基于使用单个流的过滤器来调度不同的动作。这是当前代码:

const streamEpic = (action$) => action$.pipe(
    ofType('START_PLAYBACK_STREAM'),
    switchMap(playbackStream$),
    filter((playEvent) => playEvent.type === 'LOAD')),
    map((playEvent) => loadActionCreator(playEvent.fileName))
    // how can I filter on other types? 
    // and dispatch other actions?
);

我已经看到许多使用单个流和过滤器来映射不同动作的rxjs示例,例如:

playbackStream$
    .filter((playEvent) => playEvent.type === 'LOAD'))
    .map((playEvent) => loadActionCreator(playEvent.fileName));

playbackStream$
    .filter((playEvent) => playEvent.type === 'START'))
    .map((playEvent) => startActionCreator());

playbackStream$
    .filter((playEvent) => playEvent.type === 'STOP'))
    .map((playEvent) => stopActionCreator());

我正在尝试通过Redux可观察到的方法来做同样的事情,但是没有运气。如果我使用tap,ignoreElements和store.dispatch,我可以使以下内容起作用,但我知道它是反模式的。

const streamLoadEvents = (action$, store) => action$.pipe(
    ofType('START_PLAYBACK_STREAM'),
    tap(() => {
        playbackStream$
            .filter((playEvent) => playEvent.type === 'LOAD'))
            .map((playEvent) => store.dispatch(loadActionCreator(playEvent.fileName)));

        playbackStream$
            .filter((playEvent) => playEvent.type === 'START'))
            .map((playEvent) => store.dispatch(startActionCreator()));

        playbackStream$
            .filter((playEvent) => playEvent.type === 'STOP'))
            .map((playEvent) => store.dispatch(stopActionCreator()));
    }),
    ignoreElements()
);

我知道我也可以在map或switchMap之类的内容中使用switch或if / else语句,例如此处的答案:Redux-Observable multiple actions in single epic,但我想避免这样做,因为它相当不雅致,并且确实没有充分利用流运算符。答案在这里:https://stackoverflow.com/a/40895613/367766似乎让我更近了……

这里建议的方法是什么?您是可以给我指出的运算符或示例吗?谢谢!

1 个答案:

答案 0 :(得分:0)

您是对的,如果/ else仅应作为最后的手段使用,可能有几种方法可以解决此问题,无论如何,这是我的看法:

有一个merge方法,可以看作if / else运算符的反应等效项:您为它提供了可观察对象,并且如果它们中的任何一个发出了,则所得的可观察对象将也发出自己的价值(AC)。当新动作通过时,我们将对其进行检查,并相应地选择一个新动作创建者(AC)。让我们看一些代码:

  const filteredAction$ = action$.pipe(
      ofType('START_PLAYBACK_STREAM'),
  );

这里没什么有趣的,只是过滤掉我们不感兴趣的动作类型,然后

  const operation$ = merge(
      filteredAction$.pipe(
          filter((action) => action.payload.type === 'LOAD'),
          mapTo(loadActionCreator),
      ),
      filteredAction$.pipe(
          filter((action) => action.payload.type === 'START'),
          mapTo(startActionCreator),
      ),
      filteredAction$.pipe(
          filter((action) => action.payload.type === 'STOP'),
          mapTo(stopActionCreator),
      ),
  ).pipe(
      startWith(startActionCreator)
  );

在此,我们根据动作的有效负载来选择要使用的动作创建者(请注意,我正在关注flux standard for actions-动作的数据现在位于有效负载属性下,有一个很棒的library您可以使用它作为辅助。另外,您可能应该重命名“ type”属性以避免混淆)。

mapTo基本上告诉operation $流发出什么,您可以认为此表达式是将函数分配给某个变量以供以后使用。每次调用史诗时,我们都会在此处为其选择合适的动作创建者。 这里实际上不需要startWith(假设第一个动作始终包括 “ START”类型),并且仅出于语义目的。

最后但并非最不重要的是,史诗必须返回至少一个动作来进行分派:

return combineLatest(
      operation$,
      filteredAction$
  )
      .pipe(
          map((arg) => {
            const operation = arg[0];
            const actionObject = arg[1];
            return operation(actionObject);
          })
      )

两个流中的最新值组合在一起以形成后续动作:operation $(这将为我们提供适当的动作创建器函数)和filteredAction $(始终包含动作本身,我们可能需要一些数据)。 / p>