【问题标题】:RXJS chain subscriptions anti-pattern with NGRX使用 NGRX 的 RXJS 链订阅反模式
【发布时间】:2020-04-20 19:08:25
【问题描述】:

我有以下代码,它正在工作,但我读过链接订阅是一种反模式。在调用需要用户 authtoken 的 API 之前,我需要确保用户的登录状态为 true。

在 RXJS 中处理这个问题的正确方法是什么?

const subscrAuth = this.store
  .pipe(select(isLoggedIn))
  .subscribe(isLoggedIn => {
    console.log(isLoggedIn);
    if (isLoggedIn) {
      const subscrLoaded = this.store
        .pipe(select(hasCarriersLoaded))
        .subscribe(hasCarrierLoaded => {
          if (!hasCarrierLoaded) {
            this.store.dispatch(new CarriersPageRequested());
          }
        });
    }
  });
this.unsubscribe.push(subscrAuth);

【问题讨论】:

    标签: angular rxjs ngrx


    【解决方案1】:

    如果只是 API 调用,则无需取消订阅,因为它会自行完成。您可以使用switchMap 链接和filter 到.. 过滤:)。而且你有一个很好的 rxjs 运算符链,它可以自我完成

    this.store.pipe(
      select(isLoggedIn),
      take(1),
      filter((isLoggedIn) => isLoggedIn),
      switchMapTo(this.store),
      select(hasCarriersLoaded),
      take(1),
      filter((hasCarriersLoaded) => !hasCarriersLoaded),
    ).subscribe(() => this.store.dispatch(new CarriersPageRequested()));
    

    感觉好像可以结合两个选择:

    combineLatest([
      this.store.pipe(select(isLoggedIn)),
      this.store.pipe(select(hasCarriersLoaded))
    ]).pipe(
      take(1),
      filter(([loggedIn, hasCarriers]) => loggedIn && !hasCarriers)
    ).subscribe(() => this.store.dispatch(new CarriersPageRequested()));
    

    如果您需要在用户登录之前等待,在处理此请求时,您可以执行以下操作:

    combineLatest([
      this.store.pipe(select(hasCarriersLoaded)),
      this.store.pipe(
        select(isLoggedIn),
        filter((loggedIn) => loggedIn)
      )
    ]).pipe(
      take(1),
      filter(([hasCarriers]) => !hasCarriers)
    ).subscribe(() => this.store.dispatch(new CarriersPageRequested()));
    

    如果您还需要等待登录才能调用hasCarriersLoaded,则应在第一个示例中将filtertake(1)切换

    【讨论】:

    • 您始终需要取消订阅作为最佳做法。 Angular 确实对 http 的响应做了一个完整的处理,但如果用户在完成()被触发之前离开页面或执行一些其他操作,它仍然会引入内存泄漏。
    • 这是一个很好的答案。我必须更新 switchmapto --> switchMapTo(this.store.pipe(select(hasCarriersLoaded)))
    • @tuckerjt07 observable 自行完成。只有在您等待loggedIn 状态时,您才应该使用takeUntil(destroy)。尽管如此,对我来说,感觉上面的代码应该放在服务中,而不是放在组件中。在组件中,您最好根本不使用subscribe,而只依赖模板中的async 管道来处理订阅。干净的代码是快乐的代码
    【解决方案2】:

    这样的?

    const subscrAuth = this.store
    .pipe(
      select(isLoggedIn),
      switchMap(isLoggedIn => {
        this.store.pipe(
          select(hasCarriersLoaded),
          tap(hasCarriersLoaded => {
            this.store.dispatch(new CarriersPageRequested());
          })
         )
      })
    ).subscribe();
    this.unsubscribe.push(subscrAuth);
    

    我还建议您使用 ngOnDestroy 函数来管理您的订阅,

    onDestroy$: Subject<null> = new Subject();
    
    ngOnDestroy() {
        this.onDestroy$.next();
    }
    
    this.store
    .pipe(
      takeUntil(this.onDestroy$),
      select(isLoggedIn),
      switchMap(isLoggedIn => {
        this.store.pipe(
          select(hasCarriersLoaded),
          tap(hasCarriersLoaded => {
            this.store.dispatch(new CarriersPageRequested());
          })
         )
      })
    ).subscribe();
    

    或者如果您只进行一次调用,您也可以像这样修改管道

    this.store
    .pipe(
      take(1),
      select(isLoggedIn),
      switchMap(isLoggedIn => {
        this.store.pipe(
          select(hasCarriersLoaded),
          tap(hasCarriersLoaded => {
            this.store.dispatch(new CarriersPageRequested());
          })
         )
      })
    ).subscribe();
    

    【讨论】:

    • 谢谢,“switchMap(isLoggedIn => {”这行对我来说有问题。也感谢关于销毁订阅的建议!
    • 您应该删除 switchMap 中的 { 或将 return 添加到内部 observable 中。现在什么都没有返回。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-18
    • 1970-01-01
    • 2020-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多