Angular变化检测和OnPush策略

本文概述

你已开始将Angular用于所有喜欢的项目。你知道Angular必须提供什么, 以及如何利用它来构建出色的Web应用程序。但是, 有关Angular的某些事情, 了解它们可以使你更好地在项目中使用Angular。

数据流几乎是所有Angular的中心, 变更检测是一个值得了解的知识, 因为它可以帮助你更轻松地跟踪错误, 并为你提供一个在处理复杂数据集时进一步优化应用程序的机会。

Angular变化检测和OnPush策略

在本文中, 你将学习Angular如何检测其数据结构中的变化, 以及如何使它们不变以充分利用Angular的变化检测策略。

Angular变化检测

当你更改任何模型时, Angular会检测到更改并立即更新视图。这是Angular中的更改检测。该机制的目的是确保基础视图始终与其对应的模型保持同步。 Angular的这一核心功能是使框架适应要求的部分原因, 并且部分原因是Angular是开发现代Web应用程序的最佳选择。

Angular中的模型可能会因以下任何一种情况而发生变化:

  • DOM事件(单击, 悬停等)

  • AJAX请求

  • 计时器(setTimer(), setInterval())

变更检测器

所有Angular应用程序均由层次结构的组件树组成。在运行时, Angular为树中的每个组件创建一个单独的变更检测器类, 然后最终形成类似于组件层次结构树的变更检测器层次结构。

无论何时触发变更检测, Angular都会沿着这棵变更检测器树确定它们是否已报告变更。

更改检测周期始终对每个检测到的更改执行一次, 并且从根更改检测器开始, 并以顺序方式一直向下进行。这种顺序设计的选择很不错, 因为它以可预测的方式更新模型, 因为我们知道组件数据只能来自其父项。

变更检测器层次结构

变更检测器提供了一种跟踪组件的先前和当前状态以及其结构的方法, 以便向Angular报告变更。

如果Angular从变更检测器获取报告, 它将指示相应的组件重新渲染并相应地更新DOM。

变更检测策略

值与参考类型

为了了解变更检测策略是什么以及它为什么起作用, 我们必须首先了解JavaScript中的值类型和引用类型之间的区别。如果你已经熟悉其工作原理, 则可以跳过本节。

首先, 让我们查看值类型和引用类型及其分类。

值类型

  • boolean

  • null

  • 未定义

  • Number

  • String

为简单起见, 可以想象这些类型只是将它们的值存储在堆栈内存中(从技术上讲, 这不是正确的, 但对于本文来说足够了)。例如, 在下面的图像中查看堆栈存储器及其值。

堆栈记忆体

参考类型

  • 数组

  • 对象

  • 函数

这些类型要复杂一些, 因为它们将引用存储在堆栈存储器中, 该引用指向堆存储器中的实际值。你可以在下面的示例图中查看堆栈内存和堆内存如何协同工作。我们看到堆栈内存引用了堆内存中引用类型的实际值。

堆栈内存和堆内存

值类型和引用类型之间的重要区别在于, 为了读取值类型的值, 我们只需要查询堆栈内存, 但是为了读取引用类型的值, 我们需要首先查询堆栈内存以获取引用, 然后使用该引用查询堆内存以找到引用类型的值。

默认策略

如前所述, Angular监视模型上的更改, 以确保它可以捕获所有更改。它将检查整个应用程序模型的先前状态和当前状态之间的任何差异。

Angular在默认更改检测策略中提出的问题是:模型中的任何值都已更改吗?但是对于引用类型, 我们可以实施策略, 以便提出更好的问题。这就是OnPush变更检测策略的用武之地。

OnPush策略

OnPush策略背后的主要思想体现在以下认识上:如果将引用类型视为不可变对象, 则可以检测值是否变化得更快。当引用类型是不可变的时, 这意味着每次更新时, 堆栈存储器上的引用都必须更改。现在我们可以简单地检查:引用类型的引用(在堆栈中)是否已更改?如果是, 则仅检查所有值(在堆上)。如果这令人困惑, 请参考前面的堆栈堆图。

OnPush策略基本上是问两个而不是一个问题。引用类型的引用是否已更改?如果是, 那么堆内存中的值是否已更改?

例如, 假设我们有一个包含30个元素的不可变数组, 并且我们想知道是否有任何更改。我们知道, 为了对不可变数组进行任何更新, 必须更改其引用(在堆栈上)。这意味着我们可以首先检查对数组的引用是否有任何不同, 这有可能使我们不必再进行30次(在堆中)检查以确定哪个元素不同。这称为OnPush策略。

因此, 你可能会问, 将引用类型视为不变是什么意思?这意味着我们从不设置引用类型的属性, 而是一起重新分配值。见下文:

将对象视为可变对象:

static mutable() {
  var before = {foo: "bar"};
  var current = before;
  current.foo = "hello";
  console.log(before === current);
  // => true
}

将对象视为不可变的:

static mutable() {
  var before = {foo: "bar"};
  var current = before;
  current = {foo "hello"};
  console.log(before === current);
  // => false
}

请注意, 在上面的示例中, 按照约定我们将引用类型”处理”为不可变的, 因此最后, 我们仍在处理可变对象, 但只是”假装”它们是不可变的。

那么如何为组件实现OnPush策略?你需要做的就是在他们的@Component批注中添加changeDetection参数。

import {ChangeDetectionStrategy, Component} from '@angular/core';

@Component({
  // ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
  // ...
}

Immutable.js

如果决定在Angular组件上使用OnPush策略, 则强制执行不变性是一个好主意。那就是Immutable.js出现的地方。

Immutable.js是Facebook创建的用于JavaScript不变性的库。它们具有许多不变的数据结构, 例如List, Map和Stack。就本文而言, 将说明”列表”和”映射”。有关更多参考, 请在此处查看官方文档。

为了将Immutable.js添加到你的项目中, 请确保进入终端并运行:

$ npm install immutable --save

还要确保从使用它的组件中的Immutable.js导入要使用的数据结构。

import {Map, List} from 'immutable';

这就是使用Immutable.js映射的方式:

var foobar = {foo: "bar"};
var immutableFoobar = Map(foobar);

console.log(immutableFooter.get("foo"));
// => bar

并且, 可以使用数组:

var helloWorld = ["Hello", "World!"];
var immutableHelloWorld = List(helloWorld);
console.log(immutableHelloWorld.first());
// => Hello
console.log(immutableHelloWorld.last());
// => World!

helloWorld.push("Hello Mars!");
console.log(immutableHelloWorld.last());
// => Hello Mars!

使用Immutable.js的缺点

使用Immutable.js有两个主要的可争论的缺点。

你可能已经注意到, 使用其API有点麻烦, 而且传统的JavaScript开发人员可能不喜欢这样。一个更严重的问题与无法为你的数据模型实现接口有关, 因为Immutable.js不支持接口。

本文总结

你可能会问, 为什么OnPush策略不是Angular的默认策略。我想这是因为Angular不想强迫JavaScript开发人员使用不可变的对象。但是, 这并不意味着你被禁止使用它。

如果这是你想在下一个Web项目中利用的东西, 那么你现在知道Angular使其轻松地转换为其他变更检测策略是多么容易。

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?