关于angular:译关于Angular脏值检查你应该知道的最新指南

80次阅读

共计 7263 个字符,预计需要花费 19 分钟才能阅读完成。

原文

Angular 脏值查看

本文提供了您须要理解的无关变更检测的所有必要信息。通过应用本文构建的演示我的项目来解释 angular 的变更检测机制。

Angular 的变更检测是该框架的外围机制,但(至多以我的教训)很难了解。更可怜的是,官方网站上没有对于此主题的官网指南。

What Is Change Detection

Angular 的两个次要指标是可预测和高效。框架须要通过组合状态和模板来在 UI 上复制应用程序的状态:

如果状态产生任何更改,也必须更新 DOM。将 HTML 与咱们的数据同步的机制称为“更改检测”。每个前端框架都应用其实现,例如 React 应用虚构 DOM,Angular 应用更改检测等等。我能够举荐文章“JavaScript 框架中的更改及其检测”,该文章很好地概述了此主题。

更改检测:数据更改后更新 DOM 的过程

作为开发人员,大多数时候咱们不须要关怀变更检测,除非咱们须要优化应用程序的性能。如果处理不当,更改检测会升高大型应用程序的性能。

How Change Detection Works 变更检测是如何工作的

变更检测周期能够分为两个局部:

  • 开发人员更新应用程序模型
  • Angular 通过从新渲染来同步 DOM 中的更新模型

让咱们更具体地看一下这个过程:

  1. 开发人员更新数据模型,例如通过更新组件绑定
  2. angular 检测变动
  3. 变更检测从上到下查看组件树中的每个组件,以查看相应的模型是否已更改
  4. 如果有新值,它将更新组件的视图(DOM)

    以下 GIF 以简化的形式演示了此过程:

该图显示了 Angular 组件树及其在应用程序疏导过程中为每个组件创立的更改检测器(CD)。该检测器将以后值与属性的先前值进行比拟。如果该值已更改,它将 ==isChanged== 设置为 ==true==。查看框架代码中的实现,这只是与 NaN 的非凡解决进行的 === 比拟。

Zone.js

个别状况下,zone 能够跟踪并拦挡任何异步工作。

Zone 通常具备以下阶段:

  • 开始稳固
  • 如果工作在区域中运行,它将变得不稳固,
  • 如果工作实现,它将再次变得稳固

Angular 在启动时修补了几个低级浏览器 API,以便可能检测到应用程序中的更改。这是应用 zone.js 实现的,该区域修补了 EventEmitter,DOM 事件侦听器,XMLHttpRequest,Node.js 中的 fs API 等 API。

简而言之,如果产生以下事件之一,则框架将触发更改检测

  • 任何浏览器事件(单击,键入等)
  • setInterval() and setTimeout()
  • HTTP 申请

Angular 应用其称为 NgZone 的区域。仅存在一个NgZone,并且仅针对此区域中触发的异步操作触发更改检测。

Performance 性能

默认状况下,如果模板值已更改,则“Angular Change Detection”将从上至下查看所有组件。

Angular 对每个组件执行更改检测的速度十分快,因为它能够应用内联缓存在毫秒内执行数千次查看,内联缓存可生成 VM 优化代码。

如果您想对此主题有更深刻的阐明,建议您观看 Victor Savkin 对于“重塑变化检测”的演讲。

只管 Angular 在后盾进行了大量优化,然而在大型应用程序上性能依然会降落。在下一章中,您将学习如何通过应用不同的变更检测策略来被动进步 Angular 性能。

Change Detection Strategies 变更检测策略

Angular 提供了两种策略来运行更改检测:

  • Default
  • OnPush

让咱们看一下每种变化检测策略。

Default Change Detection Strategy

默认状况下,Angular 应用 ChangeDetectionStrategy.Default 更改检测策略。每当事件触发更改检测(例如用户事件,计时器,XHR,promise 等)时,此默认策略都会从上到下查看组件树中的每个组件。这种不对组件的依赖项做任何假如的激进查看办法称为脏查看。它可能会对蕴含许多组件的大型应用程序的性能产生负面影响。

OnPush Change Detection Strategy

通过将 changeDetection 属性增加到组件装璜器元数据中,咱们能够切换到 ChangeDetectionStrategy.OnPush 更改检测策略:

@Component({
    selector: 'hero-card',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: ...
})
export class HeroCard {...}

这种更改检测策略能够跳过对此组件及其所有子组件的不必要查看。
下一个 GIF 演示了应用 OnPush 更改检测策略跳过组件树的各个局部:

应用此策略,Angular 晓得仅在以下状况下才须要更新组件:

  • 输出属性已更改, 标记为 @Input() 的属性;
  • 该组件或其子组件之一触发事件处理程序
  • 手动触发变化检测
  • 通过异步管道链接到模板的可察看对象收回新值,如 data | async

    让咱们认真看看这些事件类型。

    Input Reference Changes

    在默认的更改检测策略中,每当 @Input()数据被更改或批改时,Angular 将运行更改检测器。应用 OnPush 策略,仅当新援用作为 @Input()值传递时,才会触发更改检测器。

    JavaScript 中的所有内容都是按援用传递的,然而所有基元都是不可变的,并且它们的文字示意均指向雷同的基元实例 / 援用。批改对象属性或数组条目不会创立新援用,因而不会触发 OnPush 组件上的更改检测。要触发变更检测器,您须要传递一个新的对象或数组援用。

您能够应用简略 DEMO 测试此行为:

  1. 应用 ChangeDetectionStrategy.Default 批改 HeroCardComponent 的 age
  2. 验证带有 ChangeDetectionStrategy.OnPush 的 HeroCardOnPushComponent 不能反映更改的 age(通过组件四周的红色边框显示)
  3. 在“批改英雄”面板中单击“创立新对象援用”
  4. 验证是否通过更改检测查看了具备 ChangeDetectionStrategy.OnPush 的 HeroCardOnPushComponent

为避免更改检测谬误,在所有中央仅应用不可变的对象和列表应用 OnPush 更改检测来构建应用程序可能会很有用。不可变对象只能通过创立新的对象援用来批改,因而咱们能够保障:

  • 每次更改都会触发 OnPush 更改检测
  • 咱们不要忘了创立一个新的对象援用,否则可能导致谬误;

Immutable.js 是一个不错的抉择,该库为对象(地图)和列表(列表)提供了长久不变的数据结构。通过 npm 装置库提供了类型定义,以便咱们能够在 IDE 中利用类型泛型,谬误检测和主动实现性能。

Event Handler Is Triggered

如果 OnPush 组件或其子组件之一触发事件处理程序(例如单击按钮),则将触发更改检测(针对组件树中的所有组件)。

请留神,以下操作不会触发应用 OnPush 更改检测策略的更改检测:

  • setTimeOut
  • setInterval
  • Promise.resolve().then(), (of course, the same for Promise.reject().then())
  • this.http.get(‘…’).subscribe() (in general, any RxJS observable subscription)

    You can test this behavior using the simple demo:

    1. Click on “Change Age” button in HeroCardOnPushComponent which uses ChangeDetectionStrategy.OnPush
    2. 验证触发了变更检测并查看所有组件

Trigger Change Detection Manually 手动触发变更检测

存在三种手动触发更改检测的办法:

  • ChangeDetectorRef 的 detectChanges()通过牢记更改检测策略在此视图及其子级上运行更改检测。它能够与 detach()联合应用以实现本地更改检测查看。
  • ApplicationRef.tick()通过恪守组件的更改检测策略来触发整个应用程序的更改检测
  • ChangeDetectorRef 上的 markForCheck()不会触发更改检测,但会将所有 OnPush 先人标记为要查看一次,作为以后或下一个更改检测周期的一部分。即便已标记的组件应用 OnPush 策略,它也将运行更改检测。

手动运行变更检测不是黑客,但您只能在正当的状况下应用它,

下图以可视示意模式显示了不同的 ChangeDetectorRef 办法:

您能够在 DEMO 中应用“DC”(detectChanges())和“MFC”(markForCheck())按钮来测试其中一些操作。

### Async Pipe

内置的 AsyncPipe 订阅一个 observable 并返回它收回的最新值。

每次收回新值时,AsyncPipe 外部都会调用 markForCheck,请参见其源代码:

private _updateLatestValue(async: any, value: Object): void {if (async === this._obj) {
    this._latestValue = value;
    this._ref.markForCheck();}
}

如图所示,AsyncPipe 应用 OnPush 更改检测策略主动运行。因而,倡议尽可能应用它,以便当前执行从默认更改检测策略到 OnPush 的切换。

您能够在异步演示中看到这种行为。

第一个组件通过 AsyncPipe 将可察看对象间接绑定到模板

<mat-card-title>{{(hero$ | async).name }}</mat-card-title>
 hero$: Observable<Hero>;

  ngOnInit(): void {this.hero$ = interval(1000).pipe(startWith(createHero()),
        map(() => createHero())
      );
  }

而第二个组件订阅可察看对象并更新数据绑定值:

<mat-card-title>{{hero.name}}</mat-card-title>
  hero: Hero = createHero();

  ngOnInit(): void {interval(1000)
      .pipe(map(() => createHero()))
        .subscribe(() => {this.hero = createHero();
          console.log(
            'HeroCardAsyncPipeComponent new hero without AsyncPipe:',
            this.hero
          );
        });
  }

如您所见,没有 AsyncPipe 的实现不会触发更改检测,因而咱们须要为可察看对象收回的每个新事件手动调用 detectChanges()

防止变化检测循环和 ExpressionChangedAfterCheckedError

Angular 包含一种检测变化检测循环的机制。在开发模式下,框架运行两次更改检测,以查看自第一次运行以来该值是否已更改。在生产模式下,更改检测仅运行一次即可取得更好的性能。

我在 ExpressionChangedAfterCheckedError 演示中强加了该谬误,如果关上浏览器控制台,则能够看到它:

在此演示中,我通过更新 ngAfterViewInit 生命周期挂钩中的 hero 属性来强制执行谬误:

ngAfterViewInit(): void {this.hero.name = 'Another name which triggers ExpressionChangedAfterItHasBeenCheckedError';}

要理解为什么这会导致谬误,咱们须要查看更改检测运行期间的不同步骤:

如咱们所见,在出现了以后视图的 DOM 更新之后,将调用 AfterViewInit 生命周期挂钩。如果咱们更改此挂钩中的值,则它将在第二次更改检测运行中具备不同的值(如上所述,这是在开发模式下主动触发的),因而 Angular 将抛出 ExpressionChangedAfterCheckedError。

我能够强烈推荐 Max Koretskyi 撰写的无关 Angular 中的更改检测所需的所有常识,它具体探讨了驰名的 ExpressionChangedAfterCheckedError 的根底实现和用例。

没有更改检测的运行代码

能够在 NgZone 内部运行某些代码块,以便它不会触发更改检测。

  constructor(private ngZone: NgZone) {}

  runWithoutChangeDetection() {this.ngZone.runOutsideAngular(() => {
      // the following setTimeout will not trigger change detection
      setTimeout(() => doStuff(), 1000);
    });
  }

这个简略的演示提供了一个按钮来触发 Angular 区域之外的动作:

您应该看到该操作已记录在控制台中,然而 HeroCard 组件未选中,这意味着它们的边框不会变成红色。

此机制对于由量角器运行的 E2E 测试很有用,特地是如果您在测试中应用 browser.waitForAngular。将每个命令发送到浏览器后,量角器将期待,直到区域变得稳固为止。如果应用 setInterval,则区域将永远不会变得稳固,并且测试可能会超时。

RxJS 可察看对象可能产生雷同的问题,然而您须要依照 Zone.js 对非标准 API 的反对中所述,将修补版本增加到 polyfill.ts 中:

import 'zone.js/dist/zone';  // Included with Angular CLI.
import 'zone.js/dist/zone-patch-rxjs'; // Import RxJS patch to make sure RxJS runs in the correct zone

如果没有此修补程序,则能够在 ngZone.runOutsideAngular 外部运行可察看的代码,但仍能够作为工作在 NgZone 外部运行

停用变更检测

在非凡的应用状况下,有必要停用更改检测。例如,如果您应用 WebSocket 将大量数据从后端推送到前端,则相应的前端组件仅应每 10 秒更新一次。在这种状况下,咱们能够通过调用 detach()来停用更改检测,并应用 detectChanges()手动触发它:

constructor(private ref: ChangeDetectorRef) {ref.detach(); // deactivate change detection
    setInterval(() => {this.ref.detectChanges(); // manually trigger change detection
    }, 10 * 1000);
  }

在 Angular 应用程序的疏导过程中,也能够齐全停用 Zone.js。这意味着主动更改检测性能已齐全停用,咱们须要手动触发用户界面更改,例如通过调用 ChangeDetectorRef.detectChanges()。

首先,咱们须要正文掉从 polyfills.ts 导入的 Zone.js:

import 'zone.js/dist/zone';  // Included with Angular CLI.

接下来,咱们须要在 main.ts 中传递 noop 区域:

platformBrowserDynamic().bootstrapModule(AppModule, {ngZone: 'noop';}).catch(err => console.log(err));

无关停用 Zone.js 的更多详细信息,请参见文章没有 Zone.Js 的 Angular Elements。

Ivy

从 Angular 9 开始,Angular 默认应用 Ivy,它是 Angular 的下一代编译和渲染管道。

Ivy 依然以正确的程序解决所有框架生命周期挂钩,以便更改检测像以前一样工作。因而,您仍将在应用程序中看到雷同的 ExpressionChangedAfterCheckedError。

Max Koretskyi 在文章中写道:

如您所见,所有相熟的操作仍在这里。然而操作程序仿佛曾经扭转。例如,当初看来 Angular 首先查看子组件,而后才查看嵌入式视图。因为目前没有编译器能够生成适宜于测验我的假如的输入,因而我不确定。

您能够在此博文开端的“举荐文章”局部中找到另外两个与 Ivy 相干的乏味文章。

最初

Angular Change Detection 是一种弱小的框架机制,可确保咱们的 UI 以可预测和高效的形式示意咱们的数据。能够必定地说,更改检测仅实用于大多数应用程序,尤其是当它们不蕴含 50 多个组件时。

作为开发人员,您通常须要深入探讨此主题,起因有两个:

  • 您收到一个 ExpressionChangedAfterCheckedError 并须要解决它
  • 您须要进步应用程序性能

我心愿本文能够帮忙您更好地理解 Angular 的变更检测。随便应用我的演示我的项目来试用不同的变更检测策略。

举荐文章

  • Angular Change Detection – How Does It Really Work?
  • Angular OnPush Change Detection and Component Design – Avoid Common Pitfalls
  • A Comprehensive Guide to Angular onPush Change Detection Strategy
  • Angular Change Detection Explained
  • Angular Ivy change detection execution: are you prepared?
  • Understanding Angular Ivy: Incremental DOM and Virtual DOM

正文完
 0