深入理解Angular的变更检测

概述

这是一篇偏理论的文章,Angular的变更检测是这个框架的灵魂,如果我们深入理解这里边的内容,对于优化程序,解决性能问题,以及对Angular的深入理解都有很好的帮助,本文涉及到的知识点主要有: \color{#13c078}{Angular的} \color{#13c078}{变更检测策略}\color{#13c078}{手动变更检测方法}\color{#13c078}{Angular的} \color{#13c078}{ChangeDetectorRef对象详解}

哪些行为会引起Angular的变更检测?

  • DOM事件: click、submit...
  • XHR: 从服务端获取数据
  • Timers: setTimeout、setInterval

如上的所示,我们可以发现\color{#13c078}{只要发生异步操作},我们的程序状态可能发生变化,这就是Angular视图需要更新,需要进行变更检测的时候。

谁来通知Angular进行变更检测呢?

Angular有自己的zone,就是我们常见的ngZone,Angular 源码中有一个东西叫做 ApplicationRef,它监听 NgZone 的 onTurnDone 事件。只要这个事件发生,它就执行\color{#13c078}{tick()} 函数,这个函数执行变更检查,DOM就会更新,看下图示例,其实zone.js就是\color{#13c078}{帮助开发者省去了手动触发操作}

屏幕快照 2021-08-03 下午1.22.21.png

变更检测如何执行呢?

在 Angular 中,每个组件都有自己的变更检测器(change detector)。因为我们可以单独控制每个组件的变更检查何时发生以及如何执行,既然每个组件都有自己的\color{#13c078}{变更检查器} ,并且一个 Angular 应用包含着一个组件树,那么逻辑上我们也有一个\color{#13c078}{变更检测树}(change detector tree)。这棵树也可以被看成是一个\color{#13c078}{有向图},该有向图的数据总是从顶端流向底端(单向数据流)。
其实每个视图(组件)都有一个状态,也是非常重要的角色,因为根据它的值(FirstCheck、 ChecksEnabled、Errored、Destoryed),Angular决定是否对视图及其所有子视图运行或跳过脏值检测。如果ChecksEnabled是false或者视图是Errored或者Destroyed的状态,变更检测将会跳过这个视图以及它的子视图。

关于Angular的变更检测,是有两种策略,一种是\color{#13c078}{ChangeDetectionStrategy.Default策略},另一个是\color{#13c078}{ChangeDetectionStrategy.onPush}策略,接下来我们具体阐述一下~

Angular的Default策略

此默认策略,就是我们常提到的脏检查,它是只要有变化,就从根组件所有子组件进行检查(深度优先遍历)

屏幕快照 2021-08-03 下午2.08.18.png

Angular的onPush策略

如上默认的变更检测,Angular必须每次都检测所有组件,但是如果我们可以让Angular仅对应用中发生改变的状态进行变更检测,那样会更高效,一般我们会从下边↓两个方向上着手,实现更加高效且聪明的变更检测。

  • 使用不可变对象(减少对象属性的对比)

不可变对象保证了这个对象是不可变的,这意味着如果我们使用者不可变对象,同时试图改变这个对象,那我们总是会得到一个新的引用,如果我们的需求就是只要地址更改才进行变更检测,这样我们就不需要对比对象中的属性了

  • 减少检测组件个数

如果我们在组件中使用了onPush策略,变更检测会在第一次检查后被禁用,那么只有当输入属性(@Input)没有发生改变的时候,Angular 会跳过整棵子树的变更检查。此变更检测,只会在当前组件以及其子组件中生效,其相邻兄弟组件不会受影响。

屏幕快照 2021-08-02 下午7.41.09.png

使用方法如下:

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush, // 使用Angular的onPush策略
})

在onPush策略下,那些情况可以引起组件的变更检测?

  1. 只有当\color{#13c078}{输入数据(即@Input)的引用}发生变化组件才进行变化检测。
  2. \color{#13c078}{DOM事件触发时},组件才进行变化检测,其他异步事件更换数据都不会触发变更检测。(而且即使这个事件是个空的函数或者操作的不是@Input属性,也仍然会导致变更检测的发生)
  3. \color{#13c078}{手动触发}变更检测

如何手动触发变更检测呢?

通过引用\color{#13c078}{变化检测对象ChangeDetectorRef},可以手动去操作变化检测。我们可以在组件中的通过依赖注入的方式来获取该对象:

constructor(
   private changeRef: ChangeDetectorRef
){}

此变更检测对象提供了如下所示的方法

abstract class ChangeDetectorRef {

abstract markForCheck() : void 

abstract detectChanges() : void

abstract detach() : void

abstract reattach() : void 

abstract checkNoChanges() : void

}
markForCheck()

\color{#13c078}{在使用OnPush策略时},那么变化检测不会再次执行,除非手动调用该方法, 在程序中使用this.changeRef.markForCheck()\color{#13c078}{只能引起一次}变化检测,如要想要执行多次多次,则需要多次的运行这句话。具体的执行流程如下:

屏幕快照 2021-08-02 下午7.40.02.png
detectChanges()

首先他的使用与是否使用了onPush无关,他是只在当前视图和它的子视图\color{#13c078}{只运行一次变更检测},应用场景:明确知道有数据的更新,需要Angular执行变更检测的时候使用。

屏幕快照 2021-08-03 上午11.21.25.png
detach()

首先他的使用与是否使用了onPush无关,他是从变化检测树中\color{#13c078}{分离变化检测器},该组件的变化检测器将不再执行变化检测,同时其子组件也不会执行检测,除非手动调用 reattach() 方法

reattach()

首先他的使用与是否使用了onPush无关,他是\color{#13c078}{重新添加已分离的变化检测器},使得该组件及其子组件都能执行变化检测,但是如果当前的组件的父组件\color{#13c078}{没有启用变更检测}(脏检查)的话,它将\color{#13c078}{不起作用}

使用async pipe

当父组件的输入属性是用observable,那么除了使用this.changeRef.markForCheck()来进行变更检测,我们还可以在子组件中使用async pipe, 发出一个新值时,异步管道会将组件标记为要检查更改(其实也是调用了 this.changeRef.markForCheck())

<p>{{addCount | async}}</p>

ChangeDetectorRef中易混方法对比

markForCheck()与detectChanges()的对比?
  • markForCheck():

不会真正触发变成检测,而是将根组件标记为需要检查
② 基本上只在Angular的检测策略被设置成OnPush的时候会被使用到
③ 他的范围是从当前组件向上扩展到根组件

  • detectChanges():

会真正触发Angular的change detection
② 当数据更新,但是视图没有更新的时候,会用到这个方法,是用于手动触发变更检测的方法之一,当然微任务的定时器啥的也会触发变更检测
③ 更改检测将针对当前组件以及所有子组件
④ 可以与detach使用,完成局部的更新

reattach()与detectChanges()的对比?
  • detectChanges()

只会让变成检测手动执行一次

  • reattach()

是将视图加回去,变更检测会根据业务中数据的变化一直进行变化,相当于完全还原最原本的变更检测

通过一个示例更好的理解这两个方法的区别:


屏幕快照 2021-08-03 下午1.29.16.png

示例解析:

当num是5的时候,因为使用了detach(),则此视图不在进行变更检测,当我们调用RecoveryDetection(),执行reattach()的时候,页面的数据恢复递增显示,如果我们在这个方法中执行的是detectChanges(),页面的数据只会增加一次,就不在更新变化了~

总结

如果大家想真正了解Angular的变更检测,一定要动手写一写相关的demo去验证自己的猜想,否则就像之前的我一样,看了很多理论,还是不能够了解,而且还很容易弄混ChangeDetectorRef的属性~ 希望大家看到这篇文章可以更加清晰点吧~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,835评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,900评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,481评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,303评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,375评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,729评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,877评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,633评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,088评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,443评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,563评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,251评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,827评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,712评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,943评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,240评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,435评论 2 348

推荐阅读更多精彩内容