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

简介

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

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

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

在本文中,您将了解ChangeDetectionStrategyChangeDetectorRef

预约

如果您想继续阅读本文,您将需要:

  • 熟悉一些角度components可能会有所帮助。
  • 本文还参考了RxJS库,熟悉BehaviorSubjectObservable可能也会有所帮助。

ChangeDetectionStrategy示例

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

 1[label app.component.ts]
 2import { Component } from '@angular/core';
 3
 4@Component({
 5  selector: 'app-root',
 6  templateUrl: './app.component.html'
 7})
 8export class AppComponent {
 9  aquaticCreatures = ['shark', 'dolphin', 'octopus'];
10
11  addAquaticCreature(newAquaticCreature) {
12    this.aquaticCreatures.push(newAquaticCreature);
13  }
14}

模板将如下所示:

1[label app.component.html]
2<input #inputAquaticCreature type="text" placeholder="Enter a new creature">
3<button (click)="addAquaticCreature(inputAquaticCreature.value)">Add creature</button>
4
5<app-child [data]="aquaticCreatures"></app-child>

app-child组件类似于:

 1[label child.component.ts]
 2import { Component, Input } from '@angular/core';
 3
 4@Component({
 5  selector: 'app-child',
 6  templateUrl: './child.component.html'
 7})
 8export class ChildComponent {
 9  @Input() data: string[];
10}

app-child模板类似于:

1[label child.component.html]
2<ul>
3  <li *ngFor="let item of data">{{ item }}</li>
4</ul>

在浏览器中编译并访问应用程序后,您会看到一个无序列表,其中包含SharkDolphinOctopus

input]栏中输入一个水生生物,然后点击添加生物 按钮,就会将新生物添加到列表中。

当角度检测到主零部件中的数据已更改时,将更新子零部件。

现在,我们将子组件中的变化检测策略设置为OnPush

 1[label child.component.ts]
 2import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 3
 4@Component({
 5  selector: 'app-child',
 6  templateUrl: './child.component.html',
 7  changeDetection: ChangeDetectionStrategy.OnPush
 8})
 9export class ChildComponent {
10  @Input() data: string[];
11}

在浏览器中重新编译并访问应用程序后,您会看到一个未排序的列表,其中包含SharkDolphinOctopus

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

要向数据输入传递新引用,可以将Array.push替换为spread syntax(...)addAquaticCreature中:

1[label app.component.ts]
2// ...
3addAquaticCreature(newAquaticCreature) {
4  this.aquaticCreatures = [...this.aquaticCreatures, newAquaticCreature];
5}
6// ...

使用这个变量,您不再需要改变aquaticCreatures数组。您将返回一个全新的数组。

重新编译后,您应该注意到应用程序的行为与以前一样。ANGLE检测到一个对data的新引用,因此它对子组件运行其更改检测。

修改示例父组件和子组件以使用OnPush更改检测策略。

ChangeDetectorRef示例

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

ChangeDetectorRef.DetectChanges()

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

这需要将addAquaticCreature恢复为使用Array.ush

1[label app.component.ts]
2// ...
3addAquaticCreature(newAquaticCreature) {
4  this.aquaticCreatures.push(newAquaticCreature);
5}
6// ...

并添加触发renh()Button元素:

1[label child.component.html]
2<ul>
3  <li *ngFor="let item of data">{{ item }}</li>
4</ul>
5
6<button (click)="refresh()">Refresh</button>

然后将子组件修改为使用ChangeDetectorRef

 1[label child.component.ts]
 2import {
 3  Component,
 4  Input,
 5  ChangeDetectionStrategy,
 6  ChangeDetectorRef
 7} from '@angular/core';
 8
 9@Component({
10  selector: 'app-child',
11  templateUrl: './child.component.html',
12  changeDetection: ChangeDetectionStrategy.OnPush
13})
14export class ChildComponent {
15  @Input() data: string[];
16
17  constructor(private cd: ChangeDetectorRef) {}
18
19  refresh() {
20    this.cd.detectChanges();
21  }
22}

编译并在浏览器中访问应用程序后,您应该看到一个无序列表,其中包含sharkdolphinoctopus

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

ChangeDetectorRef.markForCheck()

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

本例将使用RxJSBehaviorSubject

 1[label app.component.ts]
 2import { Component } from '@angular/core';
 3import { BehaviorSubject } from 'rxjs';
 4
 5@Component({ 
 6  selector: 'app-root',
 7  templateUrl: './app.component.html'
 8})
 9export class AppComponent {
10  aquaticCreatures = new BehaviorSubject(['shark', 'dolphin', 'octopus']);
11
12  addAcquaticCreature((newAquaticCreature) {
13    this.aquaticCreatures.next(newAquaticCreature);
14  }
15}

在子组件的OnInit钩子中订阅。

您将在此处将这些水生生物添加到AquaticCreatures数组中:

 1[label child.component.ts]
 2import {
 3  Component,
 4  Input,
 5  ChangeDetectionStrategy,
 6  ChangeDetectorRef,
 7  OnInit
 8} from '@angular/core';
 9import { Observable } from 'rxjs';
10
11@Component({
12  selector: 'app-child',
13  templateUrl: './child.component.html',
14  changeDetection: ChangeDetectionStrategy.OnPush
15})
16export class ChildComponent implements OnInit {
17  @Input() data: Observable<any>;
18  aquaticCreatures: string[] = [];
19
20  constructor(private cd: ChangeDetectorRef) {}
21
22  ngOnInit() {
23    this.data.subscribe(newAquaticCreature => {
24      this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
25    });
26  }
27}

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

1[label child.component.ts]
2// ...
3ngOnInit() {
4  this.data.subscribe(newAquaticCreature => {
5    this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
6    this.cd.markForCheck();
7  });
8}
9// ...

markForCheck指示ANGLE,此特定输入在发生突变时应触发更改检测。

ChangeDetectorRef.Detach()ChangeDetectorRef.reattach()

使用ChangeDetectorRef还可以做的另一个强大功能是,使用Detachreattach方法手动完全分离和重新附加更改检测。

结论

在本文中,我们介绍了ChangeDetectionStrategyChangeDetectorRef。默认情况下,Angular将对所有组件执行更改检测。ChangeDetectionStrategyChangeDetectorRef可以应用于组件,以在新引用上执行变化检测,而不是在数据发生变化时执行变化检测。

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

Published At
Categories with 技术
Tagged with
comments powered by Disqus