如何在 Angular 中使用变更检测策略

介绍

默认情况下,每次应用程序发生变化时,Angular 2+ 都会对所有组件(从上到下)执行更改检测。用户事件或从网络请求接收到的数据可能会发生变化。

变更检测非常高效,但随着应用程序变得越来越复杂和组件数量增加,变更检测将不得不执行越来越多的工作。

一种解决方案是OnPush对特定组件使用变更检测策略。这将指示 Angular 仅在将新引用传递给这些组件及其子树时对这些组件及其子树运行更改检测,而不是在数据发生变异时。

在本文中,您将了解ChangeDetectionStrategyChangeDetectorRef

先决条件

如果你想跟随这篇文章,你需要:

  • 熟悉 Angular组件可能会有所帮助。
  • 这篇文章还参考了RxJS 库和熟悉,BehaviorSubjectObservable可能有帮助。

探索一个ChangeDetectionStrategy例子

让我们检查一个带有子组件的示例组件,该组件显示水生生物列表并允许用户向列表中添加新生物:

app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  aquaticCreatures = ['shark', 'dolphin', 'octopus'];

  addAquaticCreature(newAquaticCreature) {
    this.aquaticCreatures.push(newAquaticCreature);
  }
}

模板将类似于:

应用程序组件.html
<input #inputAquaticCreature type="text" placeholder="Enter a new creature">
<button (click)="addAquaticCreature(inputAquaticCreature.value)">Add creature</button>

<app-child [data]="aquaticCreatures"></app-child>

app-child组件将类似于:

子组件.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html'
})
export class ChildComponent {
  @Input() data: string[];
}

app-child模板将类似于:

子组件.html
<ul>
  <li *ngFor="let item of data">{{ item }}</li>
</ul>

编译和浏览器访问该应用程序后,应观察包含一个无序列表sharkdolphinoctopus

在输入字段中输入水生生物并单击添加生物按钮会将新生物附加到列表中。

当 Angular 检测到父组件中的数据发生变化时,子组件就会更新。

现在,让我们将子组件中的更改检测策略设置为OnPush

子组件.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data: string[];
}

重新编译并在浏览器访问该应用程序后,应观察包含无序列表sharkdolphinoctopus

但是,添加新的水生生物似乎不会将其附加到无序列表中。新数据仍会被推送到aquaticCreatures父组件中数组中,但 Angular 无法识别数据输入的新引用,因此它不会在组件上运行更改检测。

要传递对数据输入的新引用,您可以替换Array.pushspread syntax (...)in addAquaticCreature

app.component.ts
// ...
addAquaticCreature(newAquaticCreature) {
  this.aquaticCreatures = [...this.aquaticCreatures, newAquaticCreature];
}
// ...

通过这种变化,您不再改变aquaticCreatures数组。您正在返回一个全新的数组。

重新编译后,您应该观察到应用程序的行为与以前一样。Angular 检测到对 的新引用data,因此它在子组件上运行更改检测。

修改示例父组件和子组件以使用OnPush更改检测策略到此结束

探索ChangeDetectorRef示例

使用 的更改检测策略时OnPush,除了确保每次发生更改时都传递新引用之外,您还可以使用ChangeDetectorRef进行完全控制。

ChangeDetectorRef.detectChanges()

例如,您可以不断改变数据,然后在子组件中添加一个带有“刷新”按钮的按钮。

这将需要恢复addAquaticCreature使用Array.push

app.component.ts
// ...
addAquaticCreature(newAquaticCreature) {
  this.aquaticCreatures.push(newAquaticCreature);
}
// ...

并添加一个button触发元素refresh()

子组件.html
<ul>
  <li *ngFor="let item of data">{{ item }}</li>
</ul>

<button (click)="refresh()">Refresh</button>

然后,修改子组件以使用ChangeDetectorRef

子组件.ts
import {
  Component,
  Input,
  ChangeDetectionStrategy,
  ChangeDetectorRef
} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data: string[];

  constructor(private cd: ChangeDetectorRef) {}

  refresh() {
    this.cd.detectChanges();
  }
}

编译和浏览器访问该应用程序后,应观察包含一个无序列表sharkdolphinoctopus

向数组添加新项目不会更新无序列表。但是,按“刷新”按钮将对组件运行更改检测并执行更新。

ChangeDetectorRef.markForCheck()

假设您的数据输入实际上是一个可观察的。

这个例子将使用 RxJS BehaviorSubject

app.component.ts
import { Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Component({ 
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  aquaticCreatures = new BehaviorSubject(['shark', 'dolphin', 'octopus']);

  addAcquaticCreature((newAquaticCreature) {
    this.aquaticCreatures.next(newAquaticCreature);
  }
}

OnInit在子组件钩子中订阅它

您将在aquaticCreatures此处将水生生物添加到数组中:

子组件.ts
import {
  Component,
  Input,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  OnInit
} from '@angular/core';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
  @Input() data: Observable<any>;
  aquaticCreatures: string[] = [];

  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {
    <^>this.data.subscribe(newAquaticCreature => {
      this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
    });
  }
}

这段代码并不完整,因为新数据会改变dataobservable,所以 Angular 不会运行变化检测。解决方案是在订阅 observable 时调用ChangeDetectorRef‘s markForCheck

子组件.ts
// ...
ngOnInit() {
  this.data.subscribe(newAquaticCreature => {
    this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
    this.cd.markForCheck();
  });
}
// ...

markForCheck 指示 Angular 这个特定的输入在变异时应该触发变化检测。

ChangeDetectorRef.detach()ChangeDetectorRef.reattach()

您可以做的另一件强大的事情ChangeDetectorRef是使用detachreattach方法手动完全分离和重新附加更改检测

结论

在本文中,您了解了ChangeDetectionStrategyChangeDetectorRef默认情况下,Angular 将对所有组件执行更改检测。ChangeDetectionStrategy并且ChangeDetectorRef可以应用于组件以在新引用与数据发生变异时执行更改检测。

如果您想了解有关 Angular 的更多信息,请查看我们的 Angular 主题页面以获取练习和编程项目。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁