【问题标题】:tap() vs subscribe() to set a class propertytap() vs subscribe() 设置类属性
【发布时间】:2021-04-15 20:05:30
【问题描述】:

我对 rxjs 非常陌生,我只是想知道通过管道流并点击它来设置类属性是否可以,或者我应该在订阅中进行。对我来说,无论哪种方式都有效,只是想知道是否可以按照我认为适合我的眼睛去做,或者有什么我不知道的。

Typescript 代码演示两种方式:

export class ViewComponent implements OnInit {

  applicant = {};

  constructor(public route: ActivatedRoute, private store: Store<any>) {}

  ngOnInit() {
    this.route.paramMap.pipe(
      switchMap(params => this.store.select(state => state.applicants.entities[params.get('id')])),
      tap(applicant => this.applicant = applicant)
    ).subscribe();
  }
}

export class ViewComponent implements OnInit {

  applicant = {};

  constructor(public route: ActivatedRoute, private store: Store<any>) {}

  ngOnInit() {
    this.route.paramMap.pipe(
      switchMap(params => this.store.select(state => state.applicants.entities[params.get('id')]))
    ).subscribe(applicant => this.applicant = applicant);
  }
}

【问题讨论】:

  • 有趣的问题。我在处理路由参数时总是做第二个,但不是出于任何特殊原因。
  • 我已经投票结束,主要是基于意见,但我会选择第二个。行为没有区别,但使用 tap 意味着您将导入更多实际需要的代码。
  • 但是tap不是常用运算符之一吗?我认为无论如何您最终都会将其导入某个地方,并且在捆绑后它的大小不会有任何差异。在我看来,在管道中做所有事情更容易理解,只是在视觉上。
  • 使用 Rxjs 5.5+ 导入,您将不会使用全局 tap 导入/将其添加到 Observable 原型中。它确实会成为您程序中的额外代码。
  • 真的吗?!那么所有这些 tree-shaking、webpacking、commonchunking 等等都会导致 tap 重复十次?这对我来说很奇怪。

标签: angular typescript rxjs subscribe tap


【解决方案1】:

好问题。在 tap 运算符的 source code 中,这条评论几乎总结了它:

这个操作符对于调试 Observables 的正确值很有用 或执行其他副作用。
注意:这与 Observable 上的 subscribe 不同。如果do 返回的 Observable 没有被订阅,那么 Observer 指定的副作用永远不会发生。 do 因此只是监视现有的执行,它不会像 subscribe 那样触发执行。

您可以在 tap 中运行的任何副作用可能也可以放在 subscribe 块中。 subscribe 表明您打算积极使用源值,因为它表示“当这个 observable 发出时,我想将它的值保存在 applicants 变量中”。 tap 运算符主要用于调试,但它可以用于运行副作用。

一般来说,支持subscribe 块运行副作用,使用tap 进行调试,但请注意tap 可以在需要时做更多事情。

【讨论】:

  • 当我需要向可观察对象添加强制性副作用时,我会使用 Tap。无论订阅方式如何,它们都会始终激活
  • 我完全不同意。所有的 observables 都应该通过 tap 设置副作用,以便它们可以被返回并通过额外的管道进一步修改。一旦你强制订阅,你就失去了进一步控制 observable 的能力。恕我直言,订阅应始终为空。
  • 我可以看到它与 angular AsyncPipe 结合使用。异步管道旨在启用声明性逻辑,而无需担心订阅/取消订阅流。异步管道为您处理订阅过程。因此,假设您在模板vm$ 的表单中使用了一个模型。该模型是可观察的,将由异步管道处理,您无需订阅。但是,当提交表单时,您希望使用当前模型的快照。允许tap 操作员更新同事vm 属性似乎很有用且适用。
  • 我同意使用 subscribe 作为运行副作用的默认位置的一般想法,但是有时需要点击,因为 observable 的最终结果可能不再包含您想要的副作用值.例如,如果其中一个运算符是 reduce 或 map,则您正在寻找的值可能会消失。
【解决方案2】:

tap 在将 observable 与其订阅者分开时很有用。如果你有一个暴露了 observable 的类,你可以使用tap 来实现当有人监听 observable 时需要执行这个类的副作用。另一方面,当你从另一个类订阅它时,你可以从订阅者的角度实现副作用,使用subscribe

具有可观察的类:

public dummyObservable: Observable<number> = from([1, 2, 3, 4, 5]).pipe(
  // Side effects, executed every time I emit a value
  // I don't know which side effects implements who subscribes to me
  tap( n => console.log("I'm emitting this value:", n) )
);

订阅类:

ngOnInit(): void {
  this.dummyService.dummyObservable.subscribe(
    // Side effects, executed every time I receive a value
    // I don't know which side effects implements the observable
    data => console.log("I'm receiving this value: ", data)
  );
}

【讨论】:

    【解决方案3】:

    Michael Hladky 建议您将所有副作用都放在 tap 运算符中,他解释了原因here

    我认为这样做通常是个好主意,因为正如 Michael 所说,您可以将许多 observable 合并在一起并为所有它们创建一个订阅。

    我不知道这是否会提高性能,但当您想取消订阅以仅拥有一个订阅时,它肯定会更容易。 退订是您始终应该做的事情,以避免可能的内存泄漏或其他奇怪的行为。

    这种方法的另一个好处是,您可以轻松地暂停、恢复或完成一组 observable,方法是通过 filter 或 takeWhile 等运算符进行管道传输,或者通过另一个 observable 进行切换映射,如下所示:

    const allMergedObservables$ = merge(obs1, obs2, obs3);
    const play$ = new Subject();
    
    play$.asObservable().pipe(
        switchMap(bool => bool ? allMergedObservables$ : EMPTY)
    ).subscribe();
    
    // Putting 'true' into the play stream activates allMergedObservables$. 
    play$.next(true);
    
    // Something happens that makes you want to pause the application,
    // for instance the user opens the print dialog box,
    // so you issue 'false' in the play stream which in turn stops the
    // inner subscription of allMergedObservables$:
    play$.next(false);
    

    但是,这取决于您以及您喜欢哪种编程风格。

    【讨论】:

      【解决方案4】:

      使用AsyncPipeNgrxPushPipe

      对于那些想使用async 管道或ngrxPush 管道的人,您别无选择,只能使用tap 运算符

      在上面的例子中,我们可以有类似的东西

      export class ViewComponent implements OnInit {
        
        applicant = {};
        applicant$ = this.route.paramMap.pipe(
            switchMap(params => this.store.select(state => state.applicants.entities[params.get('id')])),
            tap(applicant => this.applicant = applicant)
          )
        constructor(public route: ActivatedRoute, private store: Store<any>) {}
      
        ngOnInit() { }
      }
      

      在html中

      使用AsyncPipe

      <ng-container *ngIf='applicant$ | async'>
      
         ...Some Html code here
      
      </ng-container>
      

      使用NgrxPushPipe(请记住,这仅适用于import { ReactiveComponent } from '@ngrx/component'

      <ng-container *ngIf='applicant$ | ngrxPush'>
       
      ...Some Html code here
      
      </ng-container>
      

      补充

      以上两个管道有助于提高代码的可维护性并减少/消除由于未订阅的 observables 而导致的内存泄漏风险

      上面的代码可以简化为

      TS 文件

      export class ViewComponent {
       
        applicant$ = this.route.paramMap.pipe(
            switchMap(params => this.store.select(state => 
              state.applicants.entities[params.get('id')])
            )
          )
        constructor(public route: ActivatedRoute, private store: Store<any>) {}
      
      }
      

      HTML 文件

      使用AsyncPipe

      <ng-container *ngIf='applicant$ | async as applicant'>
      
         ...Some Html code here
      
      </ng-container>
      

      使用NgrxPushPipe

      <ng-container *ngIf='applicant$ | ngrxPush as applicant'>
      
         ...Some Html code here
      
      </ng-container>
      

      使用NgrxPushPipe

      <ng-container *ngIf='applicant$ | ngrxPush as applicant'>
      
         ...Some Html code here
      
      </ng-container>
      

      使用ngrxLet 结构指令(也来自'@ngrx/component')

      <ng-container *ngrxLet='applicant$; let applicant'>
      
         ...Some Html code here
      
      </ng-container>
      

      【讨论】:

      猜你喜欢
      • 2013-08-12
      • 1970-01-01
      • 2013-07-16
      • 2023-03-17
      • 1970-01-01
      • 2017-10-20
      • 1970-01-01
      • 1970-01-01
      • 2011-12-10
      相关资源
      最近更新 更多