【问题标题】:Shared Data between multiple RXJS Observables多个 RXJS Observable 之间的共享数据
【发布时间】:2020-03-04 12:56:17
【问题描述】:

我正在重写一些代码以使用 RXJS 和角度异步管道。代码相当简单,我们将从数组中添加和删除项目。有没有比使用 Tap set 将状态存储在 BehaviorSubject 中更好的方法来完成操作共享数组?

this.listSubject = new BehaviorSubject(['a','b','c']);
this.addSubject = new Subject<string>();
this.addAction$ = this.addSubject.asObservable().pipe(
            withLatestForm(this.listSubject),
            map(([itemToAdd, list]) => {list.push(itemToAdd); return list;}),
            tap((data) => this.listSubject.next(data))
        );
this.removeSubject = new Subject<number>();
this.removeAction$ = this.removeSubject.asObservable().pipe(
            withLatestForm(this.listSubject),
            map(([indexToRemove, list]) => {list.splice(indexToRemove, 1); return list;}),
            tap((data) => this.listSubject.next(data))
        );
this.list$ = merge(this.addAction$, this.removeAction$);

编辑:UI 代码使用异步管道,

list$ | async 

【问题讨论】:

  • 这是一个糟糕的设计,您很可能会在控制台上看到 ExpressionChangedAfterItHasBeenCheckedError 异常,因为更改检测可以管理通过在这样的点击中改变数据所做的更改。
  • 我使用ChangeDetectionStrategy.OnPush而不是默认策略。

标签: angular typescript rxjs


【解决方案1】:

这样的工作:

export class HelloComponent {
  actionSubject = new Subject<Action>();
  action$ = this.actionSubject.asObservable();

  originalList$ = of(["a", "b", "c"]);

  list$ = merge(this.originalList$, this.action$).pipe(
    scan((acc: string[], action: any) => {
      if (action.isDelete) {
        return acc.filter(item => item !== action.item);
      } else {
        return [...acc, action.item];
      }
    })
  );

  onAdd() {
    this.actionSubject.next({ item: "z", isDelete: false });
  }

  onRemove() {
    this.actionSubject.next({ item: "b", isDelete: true });
  }
}

export interface Action {
  item: string;
  // This could instead be an Enum of operations
  isDelete: Boolean;
}

注意:我在这里对您的代码进行了堆栈闪电战:https://stackblitz.com/edit/angular-array-processing-deborahk

我创建了一个动作流,它发出一个动作对象,其中包含要添加/删除的项目以及该动作是否为删除。 (您可以将其更改为枚举以进行添加和删除)

然后我使用scan 来保留一段时间后的项目集,并简单地添加到该项目列表中或从中删除。

您的代码在app.component.ts 文件中

修改后的代码在hello.component.ts文件中。

这适用于您的场景吗?

注意:对于那些说您需要订阅的人……如果您使用异步管道,则不需要。我的用户界面如下所示:

<div>{{ list$ | async}}</div>

【讨论】:

  • 认为这个想法会很好用,因为它可以扩展以支持对数组的更多操作。我采用了您的 Action 接口并将其扩展为具有函数和 args 属性。 stackblitz.com/edit/…
【解决方案2】:

为什么不使用函数?

list$ = new BehaviorSubject(['a','b','c']);

add(val) {
  this.list$.next([...this.list$.value, val]);
}

remove(val) {
  this.list$.next(...this.list$.value.filter(v => v !== val));
}

您的代码不清楚,订阅了所有这些可观察对象的内容。你需要订阅你的 action observables 来触发任何功能。你正在改变对象。

【讨论】:

    【解决方案3】:

    一般来说,tap 的大多数用法都与 RxJS 硬衬有关。

    我最大的担忧是你真的应该通过订阅而不是点击来修改你的应用程序状态(行为主题)。您目前没有向list$ 显示任何订阅者。如果没有人订阅,那么您的 tap 将永远不会运行。另一方面,如果list$ 最终有两个订阅者,那么您的点击将针对每个事件运行两次!你可以在某处使用publish 来缓解这种情况,但我认为如果你有...

    this.listSubject = new BehaviorSubject(['a','b','c']);
    this.addSubject = new Subject<string>();
    this.addSubject.subscribe(itemToAdd => {
      const currentValue = this.listSubject.value;
      currentValue.push(itemToAdd);
      this.listSubject.next(currentValue);
    });
    this.removeSubject = new Subject<number>();
    this.removeSubject.subscribe(indexToRemove => {
      const currentValue = this.listSubject.value;
      currentValue.splice(indexToRemove, 1);
      this.listSubject.next(currentValue);
    });
    

    我担心的第二个(稍微少一点)是您从多个主题重新发送您的列表。您现在有三个主题,每个主题都可以发出相同的列表。这意味着你有三个潜在的真相来源。您还违反了 CQS(命令查询分离)。当调用addItem 时,它不需要获取新的列表值。相反,由于您的列表主题已更新,因此使用列表的任何内容都将简单地获得更新。

    额外阅读:在您的应用程序中,您有状态(列表)和可以修改状态的事件。或者,更一般地说,可以修改您的状态的“动作”。在像ngrx 这样的工具中,您将看到诸如“商店”(您的状态)和“动作”(您的事件)之类的概念来描述此模型。行为主题是ngrx store 之类的轻量级替代品。随着您的应用程序开始变得越来越复杂,您可能会从阅读 ngrx 商店等工具中获益良多。

    【讨论】:

      【解决方案4】:

      你的代码看起来很不错,我唯一能想到的就是不可变性。这样,您每次添加或删除项目时都会获得一个新列表:

          this.addAction$ = this.addSubject.asObservable().pipe(
                      withLatestForm(this.listSubject),
                      map(([itemToAdd, list]) => [...list, itemToAdd]),
                      tap((data) => this.listSubject.next(data))
                  );
      
          this.removeAction$ = this.removeSubject.asObservable().pipe(
                      withLatestForm(this.listSubject),
                      map(([indexToRemove, list]) =>
      [...list.slice(0, indexToRemove), ...list.slice(indexToRemove + 1)]),
                      tap((data) => this.listSubject.next(data))
                  );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-17
        • 1970-01-01
        • 2019-04-08
        相关资源
        最近更新 更多