RxJS Observable Share startWith() state in merge()
Posted
技术标签:
【中文标题】RxJS Observable Share startWith() state in merge()【英文标题】: 【发布时间】:2019-06-13 19:28:01 【问题描述】:如何在 3 个流之间共享 startWith(false)
状态?我尝试使用withLatestFrom()
,但该值出现了一些奇怪的错误。
const Home = componentFromStream(prop$ =>
const handler: toggleHandler, stream: toggle$ = createEventHandler();
const handler: showHandler, stream: show$ = createEventHandler();
const handler: hideHandler, stream: hide$ = createEventHandler();
const modal$ = merge(
toggle$.pipe(
startWith(false),
map(() => prev => !prev),
scan((state, changeState: any) => changeState(state))
),
show$.pipe(
startWith(false),
map(() => prev => true),
scan((state, changeState: any) => changeState(state))
),
hide$.pipe(
startWith(false),
map(() => prev => false),
scan((state, changeState: any) => changeState(state))
)
);
return combineLatest(prop$, modal$).pipe(
map(([props, modal]) =>
console.log(modal);
return (
<div>
<button onClick=toggleHandler>Toggle</button>
<button onClick=showHandler>Show</button>
<button onClick=hideHandler>Hide</button>
<h1>modal ? 'Visible' : 'Hidden'</h1>
</div>
);
)
);
);
在示例中,toggle
不尊重show
或hide
的当前值,而只尊重其自身的最新值。
https://stackblitz.com/edit/react-rxjs6-recompose
【问题讨论】:
merge
同时订阅所有源 Observable,因此所有三个源都会立即发出 startWith(false)
,这不是你想要的吗?
【参考方案1】:
为了做到这一点,您需要稍微不同地管理状态,类似于人们在 redux 中所做的事情。看例子:
const of, merge, fromEvent = rxjs; // = require("rxjs")
const map, scan = rxjs.operators; // = require("rxjs/operators")
const toggle$ = fromEvent(document.getElementById('toggle'), 'click');
const show$ = fromEvent(document.getElementById('show'), 'click');
const hide$ = fromEvent(document.getElementById('hide'), 'click');
const reduce = (state, change) => change(state);
const initialState = false;
const state$ = merge(
of(e => e),
toggle$.pipe(map(e => state => !state)),
show$.pipe(map(e => state => true)),
hide$.pipe(map(e => state => false)),
).pipe(
scan(reduce, initialState),
);
state$.subscribe(e => console.log('state: ', e));
<script src="https://unpkg.com/rxjs@6.2.2/bundles/rxjs.umd.min.js"></script>
<button id="toggle">Toggle</button>
<button id="show">Show</button>
<button id="hide">Hide</button>
要更好地理解它的工作原理,请查看 rxjs 文档中的 Creating applications 文章
【讨论】:
这是恕我直言不正确的解决方案。首先它不会发出任何东西,直到其中一个可观察对象发出。第二个也是最重要的切换仅适用于切换按钮的最后状态。因此,在您使用显示/隐藏按钮更改值后,它不会切换。 @MartinNuc 当我测试它时,切换似乎考虑了隐藏/显示的状态 我认为语义上使用startWith()
设置值比在scan()
中设置初始值更直观【参考方案2】:
首先在所有这些 observables 上使用 startWith
意味着它们都会在开始时发出 false。这就像你一次按下了所有这些按钮。
我认为您应该尝试实现不同的行为。使用startWith
你想设置modal
属性的初始状态,对吧?因此它应该在将这些流合并在一起之后。
要切换值,您需要两件事:
-
存储状态的地方(通常是
scan
的累加器)
区分这些按钮按下。您有三个按钮,因此需要三个值。
这是我的方法:
const modal$ = merge(
show$.pipe(mapTo(true)),
hide$.pipe(mapTo(false)),
toggle$.pipe(mapTo(null))
).pipe(
startWith(false),
scan((acc, curr) =>
if (curr === null)
return !acc;
else
return curr;
)
);
显示按钮总是发出true
,隐藏按钮发出false
,切换按钮发出null
。我将它们合并在一起,我们想从false
开始。接下来是scan
,它将状态保存在累加器中。
当null
出现时,它返回否定状态。当 true
或 false
出现时,它会返回它 - 这样它就会设置新状态,而不管之前的值。
【讨论】:
感谢您抽出宝贵时间回答这个问题!答案有效,但将切换逻辑移到扫描中似乎有点难以阅读,另一种方法似乎更直观。 没问题。传递一个函数是非常优雅的解决方案,我没有想到。一个人总能学到新东西:-)【参考方案3】:我采用了每个人的方法并想出了这个。它将 toggle() 逻辑保留在 map 函数中,并使用 startWith() 设置值。
const modal$ = merge(
toggle$.pipe(
map(() => prev => !prev),
),
show$.pipe(
map(() => prev => true),
),
hide$.pipe(
map(() => prev => false),
)
).pipe(
startWith(false),
scan((state, change) => change(state)),
);
【讨论】:
以上是关于RxJS Observable Share startWith() state in merge()的主要内容,如果未能解决你的问题,请参考以下文章
ionic - RXJS 错误:rxjs_Observable__.Observable.combineLatest 不是函数
从角度 4 中的“rxjs/Observable”导入 Observable 时出错
Rxjs:Observable.combineLatest vs Observable.forkJoin
Angular js 2 'node_modules/rxjs/Observable"' 没有导出的成员 'Observable'。导入 Observable