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 不尊重showhide 的当前值,而只尊重其自身的最新值。

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 出现时,它返回否定状态。当 truefalse 出现时,它会返回它 - 这样它就会设置新状态,而不管之前的值。

【讨论】:

感谢您抽出宝贵时间回答这个问题!答案有效,但将切换逻辑移到扫描中似乎有点难以阅读,另一种方法似乎更直观。 没问题。传递一个函数是非常优雅的解决方案,我没有想到。一个人总能学到新东西:-)【参考方案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

rxjs Observable 导入问题

Angular js 2 'node_modules/rxjs/Observable"' 没有导出的成员 'Observable'。导入 Observable

找不到模块 'rxjs/internal/Observable'