模板中同一个可观察对象上的多个异步管道是否会导致更多更改检测?

Jam*_*ing 1 angular async-pipe

我正在研究遗留的角度代码,原来的开发人员已经走了。我在模板中看到了很多异步管道。它会导致更多的变化检测吗?

模板:

      <div>{{(cart$ | async)?.name}}</div>
      <div>{{(cart$ | async)?.price}}</div>
      <div>{{(cart$ | async)?.count}}</div>
Run Code Online (Sandbox Code Playgroud)

我应该重构代码,让父组件模板异步管道cart$并将其传递给子组件吗?我知道这样代码更干净,除此之外,还有其他优点吗?谢谢!

<child [cart]="cart$ | async"></child>
Run Code Online (Sandbox Code Playgroud)

D M*_*D M 6

对同一个可观察值多次使用是否会AsyncPipe导致额外的更改检测周期?不,不一定。当 observable 发出时,所有订阅者(每次使用一个订阅者AsyncPipe)都会标记该组件以进行更改检测,但是一旦标记了某个组件,它将在下一个周期中进行检查。在同一周期中再次将其标记为更改没有任何效果。

通过多个订阅会遇到的问题AsyncPipe是每个订阅都是潜在的 API 调用或副作用触发器。

对于简单的可观察量,这不是问题。data$在下面的示例中,我们进行多个订阅并不重要。

data$ = new BehaviorSubject<int>(0);
Run Code Online (Sandbox Code Playgroud)
<p>{{ data$ | async }}</p>
<p>{{ data$ | async }}</p>
<p>{{ data$ | async }}</p>
Run Code Online (Sandbox Code Playgroud)

即使是很小的变化也会导致这成为一个问题。考虑我们必须触发一些副作用的情况:

counter = 0;
data = new BehaviorSubject<int>(0);
data$ = this.data.pipe(
  tap(() => console.log(++this.counter))
);
Run Code Online (Sandbox Code Playgroud)
<p>{{ data$ | async }}</p>
<p>{{ data$ | async }}</p>
<p>{{ data$ | async }}</p>
Run Code Online (Sandbox Code Playgroud)

我们期望每次data发出一个新值时,我们的模板都会更新,并且我们会看到计数器增加一。我们实际看到的是计数器增加了 1 三次。每个订阅都会重新触发 中的副作用tap。您可以想象,如果副作用代价高昂,如果副作用改变了临界状态,或者如果我们使用//switchMap从流中执行某些 API 调用,情况会变得多么糟糕。concatMapmergeMap


如果您确实只需要创建单个订阅,则可以通过以下两种方式之一执行此操作:ng-container或将订阅提取到父组件中。

如果孩子应该管理订阅,则可以将其留给孩子。您可以使用如下模式订阅一次并共享结果。

<ng-container *ngIf="data$ | async as data">
  <p>{{ data }}</p>
  <p>{{ data }}</p>
  <p>{{ data }}</p>
</ng-container>
Run Code Online (Sandbox Code Playgroud)

如果子级仅仅是展示性的(它不知道数据来自哪里,不修改数据等),那么您可能应该将订阅提取到父级。您甚至可能希望设置子级的更改检测策略,以OnPush避免触发更改检测,除非父级提供新数据。

@Component({
  selector: 'app-child',
  template: `
    <p>{{ data }}</p>
    <p>{{ data }}</p>
    <p>{{ data }}</p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data: int;
}
Run Code Online (Sandbox Code Playgroud)
@Component({
  selector: 'app-parent',
  template: `
    <app-child [data]="data$ | async"></app-child>
  `
})
export class ParentComponent {
  counter = 0;
  data = new BehaviorSubject<int>(0);
  data$ = this.data.pipe(
    tap(() => console.log(++this.counter))
  );
}
Run Code Online (Sandbox Code Playgroud)