如何在Angular中使用变更检测策略
介绍
默认情况下,每当您的应用程序发生更改时,Angular 2+ 都会对所有组件(从上到下)执行更改检测。 用户事件或从网络请求接收到的数据可能会发生变化。
变更检测非常高效,但随着应用程序变得越来越复杂和组件数量的增加,变更检测将不得不执行越来越多的工作。
一种解决方案是对特定组件使用 OnPush 更改检测策略。 这将指示 Angular 仅在向它们传递新引用而不是在数据发生突变时对这些组件及其子树运行更改检测。
在本文中,您将了解 ChangeDetectionStrategy 和 ChangeDetectorRef。
先决条件
如果您想继续阅读本文,您将需要:
探索 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);
}
}
模板将类似于:
app.component.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 组件将类似于:
child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
@Input() data: string[];
}
app-child 模板将类似于:
child.component.html
<ul>
<li *ngFor="let item of data">{{ item }}</li>
</ul>
在浏览器中编译并访问应用程序后,您应该会看到一个包含 shark、dolphin 和 octopus 的无序列表。
在 输入 字段中键入水生生物并单击 添加生物 按钮会将新生物添加到列表中。
当 Angular 检测到父组件中的数据发生变化时,子组件会更新。
现在,让我们将子组件中的变化检测策略设置为OnPush:
child.component.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[];
}
重新编译并在浏览器中访问应用程序后,您应该会看到一个未排序的列表,其中包含 shark、dolphin 和 octopus。
但是,添加新的水生生物似乎不会将其附加到无序列表中。 新数据仍被推入父组件中的 aquaticCreatures 数组,但 Angular 无法识别数据输入的新引用,因此它不会在组件上运行更改检测。
要传递对数据输入的新引用,您可以将 Array.push 替换为 addAquaticCreature 中的 扩展语法 (...):
app.component.ts
// ...
addAquaticCreature(newAquaticCreature) {
this.aquaticCreatures = [...this.aquaticCreatures, newAquaticCreature];
}
// ...
使用这种变体,您不再需要改变 aquaticCreatures 数组。 您正在返回一个全新的数组。
重新编译后,您应该观察到应用程序的行为与以前一样。 Angular 检测到对 data 的新引用,因此它在子组件上运行其更改检测。
修改示例父子组件以使用 OnPush 更改检测策略到此结束。
探索 ChangeDetectorRef 示例
当使用 OnPush 的变化检测策略时,除了确保每次发生变化时都传递新的引用,您还可以使用 ChangeDetectorRef 进行完全控制。
ChangeDetectorRef.detectChanges()
例如,您可以不断改变您的数据,然后在子组件中有一个带有 Refresh 按钮的按钮。
这将需要恢复 addAquaticCreature 以使用 Array.push:
app.component.ts
// ...
addAquaticCreature(newAquaticCreature) {
this.aquaticCreatures.push(newAquaticCreature);
}
// ...
并添加一个触发 refresh() 的 button 元素:
child.component.html
<ul>
<li *ngFor="let item of data">{{ item }}</li>
</ul>
<button (click)="refresh()">Refresh</button>
然后,修改子组件以使用 ChangeDetectorRef:
child.component.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();
}
}
在浏览器中编译并访问应用程序后,您应该会看到一个包含 shark、dolphin 和 octopus 的无序列表。
向数组中添加新项目不会更新无序列表。 但是,按下 Refresh 按钮将对组件运行更改检测并执行更新。
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 数组中:
child.component.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];
});
}
}
此代码不完整,因为新数据改变了 data 可观察对象,因此 Angular 不会运行更改检测。 解决方案是在订阅 observable 时调用 ChangeDetectorRef 的 markForCheck:
child.component.ts
// ...
ngOnInit() {
this.data.subscribe(newAquaticCreature => {
this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
this.cd.markForCheck();
});
}
// ...
markForCheck 指示 Angular 这个特定的输入在突变时应该触发变化检测。
ChangeDetectorRef.detach() 和 ChangeDetectorRef.reattach()
使用 ChangeDetectorRef 可以做的另一件强大的事情是使用 detach 和 reattach 方法手动完全分离和重新附加更改检测。
结论
在本文中,您了解了 ChangeDetectionStrategy 和 ChangeDetectorRef。 默认情况下,Angular 将对所有组件执行更改检测。 ChangeDetectionStrategy 和 ChangeDetectorRef 可应用于组件以对新引用执行更改检测,而不是在数据发生突变时。
如果您想了解有关 Angular 的更多信息,请查看 我们的 Angular 主题页面 以获取练习和编程项目。