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

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

简介

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

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

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

在本文中,您将学习关于 ChangeDetectionStrategyChangeDetectorRef

先决条件

如果您想跟随本文,您需要:

  • 一些熟悉 Angular 组件可能会有所帮助。
  • 本文还涉及 RxJS 库,熟悉 BehaviorSubjectObservable 也可能会有所帮助。

探索 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>

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

在输入框中输入水生生物名称,然后单击 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[];
}

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

然而,添加新的水生生物似乎不会将其追加到无序列表中。新数据仍然被推送到父组件的 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();
  }
}

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

向数组添加新项不会更新无序列表。但是,单击 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 时调用 ChangeDetectorRefmarkForCheck

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

markForCheck 指示 Angular 当特定输入发生变化时应触发变更检测。

ChangeDetectorRef.detach()ChangeDetectorRef.reattach()

使用 ChangeDetectorRef 还可以完全手动分离和重新附加变更检测,通过 detachreattach 方法。

结论

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


目录
相关文章
|
2月前
|
UED
|
4月前
|
开发者 Windows Android开发
跨平台开发新选择:揭秘Uno Platform与.NET MAUI优劣对比,帮你找到最适合的框架,告别选择困难症!
【8月更文挑战第31天】本文对比了备受关注的跨平台开发框架Uno Platform与.NET MAUI的特点、优势及适用场景。Uno Platform基于WebAssembly和WebGL技术,支持Windows、iOS、Android及Web平台,而.NET MAUI由微软推出,旨在统一多种UI框架,支持Windows、iOS和Android。两者均采用C#和XAML进行开发,但在性能、平台支持及社区生态方面存在差异。Uno Platform在Web应用方面表现出色,但性能略逊于原生应用;.NET MAUI则接近原生性能,但不支持Web平台。开发者应根据具体需求选择合适的框架。
142 0
|
4月前
|
缓存 UED 开发者
全面加速Angular应用:从代码拆分到服务器端渲染的性能优化全攻略——深入探讨提升加载速度的有效策略
【8月更文挑战第31天】在现代Web开发中,提升应用加载速度对增强用户体验至关重要,尤其对于使用Angular框架的单页应用而言更是如此。本文通过解答五个常见问题,提供了一份全面的Angular性能优化攻略,涵盖减少初始加载时间、处理大型第三方库、优化变更检测、利用缓存以及服务器端渲染等技术。通过这些方法,开发者能够显著提升应用性能,确保流畅高效的用户体验。
57 0
|
移动开发 JavaScript 网络架构
Angular最新教程-第九节 路由二(路由策略、base标签、路由参数)
Angular最新教程-第九节 路由二(路由策略、base标签、路由参数)
334 0
Angular最新教程-第九节 路由二(路由策略、base标签、路由参数)
|
存储
RouteReuseStrategy angular路由复用策略详解,深度刨析路由复用策略
关于路由复用策略网上的文章很多,大多是讲如何实现tab标签切换历史数据,至于如何复用的原理讲的都比较朦胧,代码样例也很难适用各种各样的路由配置,比如懒加载模式下多级嵌套路由出口网上的大部分代码都会报错。
2723 0
|
4月前
|
API 开发者 UED
PrimeFaces:JSF的魔法衣橱,解锁UI设计的无限可能!
【8月更文挑战第31天】本文介绍如何结合 JSF(JavaServer Faces)和 PrimeFaces 构建美观且功能强大的现代用户界面。PrimeFaces 提供丰富的 UI 组件库,包括按钮、输入框、数据网格等,支持现代 Web 标准,简化界面开发。文章通过具体示例展示如何使用 `&lt;p:inputText&gt;` 和 `&lt;p:calendar&gt;` 等组件创建用户表单,并用 `&lt;p:dataTable&gt;` 展示数据集合,提升 JSF 应用的易用性和开发效率。
63 0
|
4月前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
55 0
|
4月前
|
开发者 C# C++
揭秘:如何轻松驾驭Uno Platform,用C#和XAML打造跨平台神器——一步步打造你的高性能WebAssembly应用!
【8月更文挑战第31天】Uno Platform 是一个跨平台应用程序框架,支持使用 C# 和 XAML 创建多平台应用,包括 Web。通过编译为 WebAssembly,Uno Platform 可实现在 Web 上运行高性能、接近原生体验的应用。本文介绍如何构建高效的 WebAssembly 应用:首先确保安装最新版本的 Visual Studio 或 VS Code 并配置 Uno Platform 开发环境;接着创建新的 Uno Platform 项目;然后通过安装工具链并使用 Uno WebAssembly CLI 编译应用;最后添加示例代码并测试应用。
116 0
|
4月前
|
前端开发 开发者 安全
JSF支付功能大揭秘:探索如何在Java世界中实现安全无缝的在线支付体验
【8月更文挑战第31天】在电子商务和在线交易日益普及的今天,实现在线支付功能已成为许多Web应用的必备需求。JavaServer Faces (JSF) 作为一种流行的Java Web框架,提供了丰富的组件和工具来构建用户界面,包括与支付网关集成以实现在线支付。支付网关是处理信用卡和借记卡支付的系统,在商家和银行之间起到桥梁作用。本文将探讨如何使用JSF与支付网关集成,以及实现在线支付功能时需要考虑的关键点
49 0
|
4月前
|
开发者 前端开发 开发框架
JSF与移动应用,开启全新交互体验!让你的Web应用轻松征服移动设备,让用户爱不释手!
【8月更文挑战第31天】在现代Web应用开发中,移动设备的普及使得构建移动友好的应用变得至关重要。尽管JSF(JavaServer Faces)主要用于Web应用开发,但结合Bootstrap等前端框架,也能实现优秀的移动交互体验。本文探讨如何在JSF应用中实现移动友好性,并通过示例代码展示具体实现方法。使用Bootstrap的响应式布局和组件可以确保JSF页面在移动设备上自适应,并提供友好的表单输入和提交体验。尽管JSF存在组件库较小和学习成本较高等局限性,但合理利用其特性仍能显著提升用户体验。通过不断学习和实践,开发者可以更好地掌握JSF应用的移动友好性,为Web应用开发贡献力量。
53 0