简介
默认情况下,每次应用程序发生变化时,角度2+都会对所有组件(从上到下)执行更改检测。更改可以发生在用户事件或从网络请求接收的数据中。
变更检测的性能非常高,但随着应用程序变得越来越复杂,组件数量不断增加,变更检测将不得不执行越来越多的工作。
一种解决方案是对特定组件使用OnPush
变化检测策略。这将指示ANGLE仅在向这些组件及其子树传递新引用时(而不是在数据发生突变时)运行更改检测。
在本文中,您将了解ChangeDetectionStrategy
和ChangeDetectorRef
。
预约
如果您想继续阅读本文,您将需要:
- 熟悉一些角度components可能会有所帮助。
- 本文还参考了RxJS库,熟悉
BehaviorSubject
和Observable
可能也会有所帮助。
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>
在浏览器中编译并访问应用程序后,您会看到一个无序列表,其中包含Shark
、Dolphin
和Octopus
。
在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}
在浏览器中重新编译并访问应用程序后,您会看到一个未排序的列表,其中包含Shark
、Dolphin
和Octopus
。
然而,添加一个新的水生生物似乎并没有将其添加到无序列表中。新数据仍然会被推送到父组件中的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}
编译并在浏览器中访问应用程序后,您应该看到一个无序列表,其中包含shark
、dolphin
和octopus
。
向数组中添加新项不会更新无序列表。但是,按下 刷新 按钮将对组件运行更改检测,并执行更新。
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时调用ChangeDetectorRef
的markForCheck
:
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
还可以做的另一个强大功能是,使用Detach
和reattach
方法手动完全分离和重新附加更改检测。
结论
在本文中,我们介绍了ChangeDetectionStrategy
和ChangeDetectorRef
。默认情况下,Angular将对所有组件执行更改检测。ChangeDetectionStrategy
和ChangeDetectorRef
可以应用于组件,以在新引用上执行变化检测,而不是在数据发生变化时执行变化检测。
如果您想了解更多有关ANGLE的信息,请查看我们的ANGLE主题页面以获取练习和编程项目。