简介
默认情况下,Angular 2+ 会在应用程序中的每次变化时对所有组件(从上到下)执行变更检测。变化可以来自用户事件或者从网络请求接收到的数据。
变更检测非常高效,但随着应用程序变得更加复杂并且组件数量增加,变更检测将不得不执行越来越多的工作。
其中一个解决方案是为特定组件使用 OnPush
变更检测策略。这将指示 Angular 仅在向这些组件及其子树传递新引用时才运行变更检测,而不是在数据发生变化时运行变更检测。
在本文中,您将学习关于 ChangeDetectionStrategy
和 ChangeDetectorRef
。
先决条件
如果您想跟随本文,您需要:
- 一些熟悉 Angular 组件可能会有所帮助。
- 本文还涉及 RxJS 库,熟悉
BehaviorSubject
和Observable
也可能会有所帮助。
探索 ChangeDetectionStrategy
示例
让我们来看一个带有子组件的示例组件,该子组件显示水生生物列表,并允许用户向列表中添加新的生物:
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); } }
模板如下:
<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
组件如下:
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-child', templateUrl: './child.component.html' }) export class ChildComponent { @Input() data: string[]; }
app-child
模板如下:
<ul> <li *ngFor="let item of data">{{ item }}</li> </ul>
编译并在浏览器中访问应用程序后,您应该看到一个无序列表,其中包含 shark
、dolphin
和 octopus
。
在输入框中输入水生生物名称,然后单击 Add creature 按钮将新生物添加到列表中。
当 Angular 检测到父组件中的数据发生变化时,子组件将会更新。
现在,让我们将子组件的变更检测策略设置为 OnPush
:
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 并未识别到数据输入的新引用,因此它不会对组件运行变更检测。
要向数据输入传递新引用,您可以在 addAquaticCreature
中使用 spread syntax (...)
替换 Array.push
:
// ... addAquaticCreature(newAquaticCreature) { this.aquaticCreatures = [...this.aquaticCreatures, newAquaticCreature]; } // ...
使用这种变化后,您不再对 aquaticCreatures
数组进行突变。您将返回一个全新的数组。
重新编译后,您应该观察到应用程序的行为与之前相同。Angular 检测到了对 data
的新引用,因此它对子组件运行了变更检测。
这就完成了修改示例父子组件以使用 OnPush
变更检测策略。
探索 ChangeDetectorRef
示例
在使用 OnPush
变更检测策略时,除了确保每次应该发生变化时都传递新引用之外,您还可以利用 ChangeDetectorRef
来完全控制。
ChangeDetectorRef.detectChanges()
例如,您可以继续对数据进行突变,然后在子组件中添加一个带有 Refresh 按钮的按钮。
这将需要将 addAquaticCreature
恢复为使用 Array.push
:
// ... addAquaticCreature(newAquaticCreature) { this.aquaticCreatures.push(newAquaticCreature); } // ...
并添加一个触发 refresh()
的 button
元素:
<ul> <li *ngFor="let item of data">{{ item }}</li> </ul> <button (click)="refresh()">Refresh</button>
然后,修改子组件以使用 ChangeDetectorRef
:
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()
假设你的数据输入实际上是一个 observable。
这个例子将使用 RxJS 的 BehaviorSubject
:
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
数组中:
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
observable,所以 Angular 不会运行变更检测。解决方案是在订阅 observable 时调用 ChangeDetectorRef
的 markForCheck
:
// ... 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
可以应用于组件,以在新引用与数据发生变化时执行变更检测。