【问题标题】:rxjs - how can you create another observable from an observable but ignore its output in the parent observable?rxjs - 你如何从一个 observable 创建另一个 observable 但忽略它在父 observable 中的输出?
【发布时间】:2021-05-09 01:28:22
【问题描述】:

我有一种情况,我有一个 observable,对于每个发射的项目,我想创建另一个 observable,但忽略该 observable 的值,而是返回第一个 observable 的结果。
例如,如果我单击一个按钮,我想跟踪另一个按钮中发生的事情,仅当第一个按钮被打开时。

我现在可以做到这一点,有点像 hack,通过获取子 observable 的输出并将其传递到带有父值的mapTo。你可以在这段代码中看到它,可以在code sandbox中玩:

import { fromEvent, from } from "rxjs";
import { mapTo, switchMap, tap, scan  } from "rxjs/operators";

const buttonA = document.getElementById("a");
const buttonB = document.getElementById("b");

const textA = document.querySelector('#texta');
const textB = document.querySelector('#textb');

fromEvent(buttonA, 'click').pipe(
  // this toggles active or not.
  scan((active) => !active, false),
  switchMap(active => {

    if (active) {
      const buttonBClicks$ = fromEvent(buttonB, 'click');
 
      // here we can observe button b clicks, when button a is toggled on.
      return buttonBClicks$.pipe(
        // count the sum of button b clicks since button a was toggled on.
        scan((count) => count+1, 0),
        tap(buttonBCount  => {
          textB.value = `button b count ${buttonBCount}`;
        }),
        // ignore the value of the button b count for the final observable output. 
        mapTo(active)
      )
    } else {
      textB.value = ``;

      return from([active]);
    }
   
  })
).subscribe({
  next: buttonActive => {
    textA.value = `Button a active: ${buttonActive}`
  }
});

这里有几个问题。在按钮被打开的情况下,外部 observable 只有在单击按钮后才会收到一个值。 这个mapTo 的用法看起来很老套。

有更好的方法吗?

【问题讨论】:

    标签: rxjs rxjs-observables rxjs-pipeable-operators


    【解决方案1】:

    听起来您根本不希望内部可观察对象实际上成为该过程的一部分。你在等它还是什么?

    如果没有,您可以将其作为副作用进行如下操作:

    fromEvent(buttonA, 'click').pipe(
      scan((active) => !active, false),
      tap(active => { if(active) {
        fromEvent(buttonB, 'click').pipe(
          scan(count => count+1, 0),
          tap(buttonBCount  => {
            textB.value = `button b count ${buttonBCount}`;
          })
        ).subscribe()   
      }})
    ).subscribe({
      next: buttonActive => {
        textA.value = `Button a active: ${buttonActive}`
      }
    });
    

    嵌套订阅被认为是糟糕的巫术,因此您可以像这样重构以使您的 conserns 分离更加明显:

    const trackActiveFromButton$ = fromEvent(buttonA, 'click').pipe(
      scan((active) => !active, false),
      shareReplay(1)
    );
    
    trackActiveFromButton$.subscribe({
      next: buttonActive => {
        textA.value = `Button a active: ${buttonActive}`
      }
    });
    
    trackActiveFromButton$.pipe(
      switchMap(active => active ?
        fromEvent(buttonB, 'click').pipe(
          scan(count => count+1, 0),
          tap(buttonBCount  => {
            textB.value = `button b count ${buttonBCount}`;
          })
        ) :
        EMPTY
      )
    ).subscribe();
    

    【讨论】:

      【解决方案2】:

      有更好的方法吗?

      根据您的口味,以下可能会更好。在我看来,您的示例代码有点混乱,因为您有一个可观察的对象正在尝试做太多事情。并且副作用与流行为逻辑混在一起。

      使用tap() 做副作用类型的事情完全没问题,但有时它会让人更难理解。特别是在上面的代码中,因为涉及到一个嵌套的 observable。

      创建始终发出特定数据的单独可观察对象可以使事情更容易理解。

      如果我们声明一个流来表示isActive 状态并订阅它来更新textA,并定义一个counter 流来表示在isActive = true 期间发生的点击次数,使用该值来更新textB,我认为这样可以更轻松地了解正在发生的事情:

      const clicksA$ = fromEvent(buttonA, 'click');
      const clicksB$ = fromEvent(buttonB, 'click');
      
      const isActive$ = clicksA$.pipe(
        scan(active => !active, false),
        startWith(false)
      );
      
      const counterB$ = combineLatest([isActive$, clicksB$]).pipe(
        scan((count, [isActive]) => isActive ? count + 1 : -1, 0)
      );
         
      counterB$.subscribe(
        count => textB.value = count === -1 ? '' :`button b count ${count}`
      );
      
      isActive$.subscribe(
          isActive => textA.value = `Button a active: ${isActive}`
      );
      

      对我来说,单独定义流可以更容易地查看它们之间的关系,这意味着更容易判断它们何时发出:

      • isActive 派生自 clicksA
      • counterB 派生自 clicksBisActive

      这是一个有效的StackBlitz

      还有:

      只有在点击按钮后,外部 observable 才会收到一个值

      这可以使用startWith() 发出默认值来解决。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-19
        • 2019-01-04
        相关资源
        最近更新 更多