当使用redux-observable时,从单个动作和单个流中,如何根据过滤器调度不同的动作

问题描述 投票:0回答:1

更新

这是一个使用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-observable中做同样的事情,但没有运气。如果我使用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似乎让我更接近......

建议的方法是什么?您可以指示我的操作员或示例吗?谢谢!

redux rxjs observable redux-observable
1个回答
0
投票

你是对的,if / else应该只是作为最后的手段而且你可能有几种方法可以解决这个问题,无论如何,这是我对此的看法:

有一个merge方法,它可以被视为if / else运算符的反应等价物:你为它提供了observables,如果它们中的任何一个发出,结果observable也会发出它自己的值(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基本上告诉操作$ stream要发出什么,你可以把这个表达式想象为为某个变量分配一个函数供以后使用。每次调用一个史诗时,我们都会在这里为它选择一个合适的动作创建者。这里实际上不需要startWith(假设第一个动作总是包含'START'类型)并且仅用于语义目的。

最后但并非最不重要的是,史诗必须至少返回一个调度动作:

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

两个流的最新值组合在一起形成一个后续操作:operation $(它产生一个适当的动作创建函数)和filteredAction $(总是包含动作本身,我们可能需要一些来自它的数据)。

© www.soinside.com 2019 - 2024. All rights reserved.