龙空技术网

「Angular项目实战」Angular2的脏值检测机制

博读代码 42

前言:

如今同学们对“angular请求数据封装”大约比较看重,兄弟们都想要分析一些“angular请求数据封装”的相关知识。那么小编在网络上汇集了一些对于“angular请求数据封装””的相关文章,希望我们能喜欢,姐妹们快快来学习一下吧!

Change Detection (变化检测) 是 Angular 2 中最重要的一个特性。当组件中的数据发生变化的时候,Angular 2 能检测到数据变化并自动刷新视图反映出相应的变化。

那么,Angular 2 是如何知道数据发生了改变?又是如何知道需要修改的 DOM 位置,准确地用最小范围去修改 DOM 呢?

本期跟大家分享一下,Angular2的脏值检测机制。

NgZone

在 Angular 2 中,有一个 NgZone,它是专门为 Angular 2 定制的 zone。

zone.js 这个工具给所有 JavaScript 异步事件 都提供了一个上下文。zone.js 可以实现异步任务的跟踪、分析、错误记录。NgZone 是基于 Zone 实现的,它是 Zone 派生出来的一个子 Zone,在 Angular 环境内注册的异步事件都运行在这个子 Zone 内 (因为 NgZone 拥有整个运行环境的执行上下文),它扩展了自有的一些 API,并添加了一些功能性的方法到它的执行上下文中。

在 Angular 源码中,有一个 ApplicationRef_ 类,其作用是用来监听 NgZone 中的 onMicrotaskEmpty 事件。无论何时,只要触发这个事件,那么将会执行一个 tick 方法用来告诉 Angular 去执行变化检测。

Angular 会在初始化的时候调用 zone,下面的代码是 Angular 的 ApplicationRef_ 的构造函数中的一部分,this._zone 是 NgZone 的一个实例。而NgZone 是 zone 的一个简单封装,当异步事件结束的时候由 onMicrotaskEmpty 提示 Angular 更新视图。

this._zone.onMicrotaskEmpty.subscribe({  next: () => {    this._zone.run(() => { this.tick();});  }});

tick() 函数会对所有附在 ApplicationRef_ 上的视图进行脏检查。

这也就是为什么我们在需要手动调用脏检查的时候,一般会使用 tick() 或 setTimeout() 的原因。

tick(): void {  this._views.forEach((view) => view.ref.detectChanges())

用过 Angular 1.x 的同学,应该很清楚,当我们使用第三方库方法或 settimeout 的时候,由于脱离了 angular 上下文了,需要用 $timeout 服务或手动调用 $scope.$digest() 方法来通知视图刷新。

这对于初学者来说,是很麻烦的一件事情。但在 angular2 中,不需要再使用 Angular 1.x 中的 $timeout 服务或手动调用 $scope.$digest() 方法来刷新视图。

那么,Angular 2 是如何做到模型发生变化后,自动通知视图进行刷新呢?

其实在 Angular 2 应用程序启动之前,Zone 采用猴子补丁 (Monkey-patched) 的方式,将 JavaScript 中的异步任务都进行了包装,这使得这些异步任务都能运行在 Zone 的执行上下文中,每个异步任务在 Zone 中都是一个任务。

除了提供一些供开发者使用的钩子外,默认情况下 Zone 重写了以下方法:

setInterval、clearInterval、setTimeout、clearTimeout

alert、prompt、confirm

requestAnimationFrame、cancelAnimationFrame

addEventListener、removeEventListener

脏检查过程

在 Angular 中,每一个组件都有它自己的检测器(detector),用于负责检查其自身模板上绑定的变量,所以每一个组件都可以独立地决定是否进行脏检查。

因为在 Angular 中组件是以树的形式组织起来的,相应地,检测器也是一棵树的形状。

当一个异步事件发生时,脏检查会从根组件开始,自上而下对树上的所有子组件进行检查。

相比 Angular1 中的带有环的结构,这样的单向数据流效率更高,而且容易预测。

(1)child.component.ts

import { Component, Input } from '@angular/core';@Component({    selector: 'exe-child',    template: `     <p>{{ text }}</p>    `})export classChildComponent {    @Input() text: string;}

(2)parent.component.ts

import { Component, Input } from '@angular/core';@Component({    selector: 'exe-parent',    template: `     <exe-child [text]="name"></exe-child>     `})export classParentComponent {    name: string = 'Semlinker';}

(3)app.component.ts

import { Component } from '@angular/core';@Component({  selector: 'exe-app',  template: `    <exe-parent></exe-parent>  `})export classAppComponent{ }

上面的例子中,ParentComponent 组件会比 ChildComponent 组件更早执行变化检测。

因此,在执行变化检测时 ParentComponent 组件中的 name 属性,会传递到 ChildComponent 组件的输入属性 text 中。

此时,ChildComponent 组件检测到 text 属性发生变化,因此组件内的 p 元素内的文本值从空字符串变成 ‘Semlinker’ 。

这看起来虽然很简单,但非常重要。另外,对于单次变化检测,每个组件只检查一次。

脏检查策略:OnPush

现在默认脏检查方法是从根组件开始,遍历所有的子组件进行脏检查,但这种检查方式的性能存在很大问题。

如果我们能让组件只在其输入改变的时候才进行脏检查,那性能会得到大大提高。

Angular 提供了 OnPush 脏检查策略,可以用下面的方式使用:

@Component({  selector: 'todos',  changeDetection: ChangeDetectionStrategy.OnPush,  templateUrl: 'todos.component.html'})export classTodosComponent{  @Input()  todos: Todo[];}

使用 OnPush 后,组件只有在输入改变的时候才会进行脏检查,这里的改变是指:使用 === 判断为 false。

因此在上面的例子中,即使往 todos 数组中通过 push 添加新数据也不会触发脏检查,只有给 todos 重新赋值才会触发。

这样子,我们就有机会在脏检查中跳过一个组件的子树,减少检查次数。

小结

Angular2 在 Zone 的基础上进行封装了自己的 NgZone,实现了脏值检查自动更新的机制,相比于 Angular1 来说使用体验更好。另外,我们也可以根据自己的需要使用 OnPush 进行性能提升。

下期给大家分享更多实战中的点滴,如果大家喜欢 Angular 或对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!

标签: #angular请求数据封装