这是一个使用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似乎让我更接近......
建议的方法是什么?您可以指示我的操作员或示例吗?谢谢!
你是对的,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 $(总是包含动作本身,我们可能需要一些来自它的数据)。