ChangeDetection
概述
简单来说变化检测就是Angular用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。
当组件实例化之后,Angular 就会创建一个变更检测器,它负责传播组件各个绑定值的变化。 该策略有下列值:
changeDetection:ChangeDetectionStrategy.Default
默认策略,执行脏检查,只要值发生变化,就从父组件到子组件进行全面变更检测。检测全面,但当组件很多时,检测效率低。
changeDetection:ChangeDetectionStrategy.OnPush
当输入数据@Input的引用发生变化或者有事件触发时,组件才进行变更检测。检测不全面,但检测效率高。
变更检测的概念
组件内的数据状态变化以后,需要对应更新视图。这种将视图和数据同步的机制,就叫变化检测。核心永远都是旧数据和新数据的对比。
变更检测的触发条件
只要发生了异步操作(Events, Timer, XHR),Angular 就会认为有状态可能发生变化了,然后就会进行变更检测。
- Events::click,mouseover,mouseout,keyup,keydown 等浏览器事件;
- Timer:setTimeout/setInterval;
- XHR:各类请求等。
既然都是对异步操作进行变更检测,那么Angular是如何订阅异步请求,进行变更检测的呢?
这里介绍下NgZone以及它的fork对象Zone.js。
Zone.js 用于封装和拦截浏览器中的异步活动、它还提供 异步生命周期的钩子 和 统一的异步错误处理机制。
Zone.js 是通过 Monkey Patch(猴子补丁) 的方式来对浏览器中的常见方法和元素进行拦截,例如 setTimeout 和 HTMLElement.prototype.onclick。Angular 在启动时会利用 Zone.js 修补几个低级浏览器 API,从而实现异步事件的捕获,并在捕获时间后调用变更检测。
Angular通过forkZone.js并拓展出一个自己的区域NgZone,让应用中的所有异步操作都会运行在这个区域中。
Angular的变更检测如何工作的?
Angualr会为每一个组件生成一个变化监测器changeDetector ,记录组件的变化状态。
我们在创建了一个Angular 应用后,Angular 会同时创建一个 ApplicationRef 的实例,这个实例代表的就是我们当前创建的这个 Angular 应用的实例。 ApplicationRef 创建的同时,会订阅 ngZone 中的 onMicrotaskEmpty 事件,在所有的微任务完成后,遍历并调用所有的视图的detectChanges()来执行变更检测。
变更检测的执行顺序
- 更新所有子子组件绑定的属性
- 调用所有子组件生命周期的钩子 OnChanges, OnInit, DoCheck,AfterContentInit
- 更新当前组件的DOM
- 调用子组件的变更检测(即所有子组件循环上述3步)
- 调用所有子组件的生命周期钩子 ngAfterViewInit
变更检测的执行策略
-
Default 策略
每次 事件 触发 变化检测(如用户事件、计时器、XHR、promise 等)时,此默认策略都会从上到下检查组件树中的每个组件。这种对组件的依赖关系不做任何假设的保守检查方式称为 脏检查,这种策略在我们应用组件过多时会对我们的应用产生性能的影响。 -
OnPush 策略
修改组件装饰器的changeDetection,设置为 OnPush 策略后,Angular 每次触发变化检测后会跳过该组件和该组件的所以子组件变化检测。
@Component({
selector: 'app-xxx',
templateUrl: 'XXX',
styleUrls: ['XXX'],
changeDetection:ChangeDetectionStrategy.OnPush
})
在 OnPush 策略下,只有以下这几种情况才会触发组件的变化检测:
-
输入值(@Input)更改(入input的值必须是一个新的引用)
-
当前组件或子组件之一触发了事件 (但在onPush策略中,以下操作不会触发变更检测)
- setTimeout()
- setInterval()
- Promise.resolve().then()
- this.http.get(‘…’).subscribe()
-
手动触发变更检测(每个组件都会关联一个组件视图ChangeDetectorRef)
-
detectChanges(): 它会触发当前组件和子组件的变化检测
-
markForCheck():它不会触发变化检测,但是会把当前的OnPush组件和所以的父组件为OnPush的组件 标记为需要检测状态,在当前或者下一个变化检测周期进行检测
-
ApplicationRef.tick() :它会根据组件的变化检测策略,触发整个应用程序的更改检测
-
async pipe
-
参考链接:https://juejin.cn/post/7093770971588329508
ChangeDetectorRef
概述
实时检测数据的变化并更新视图数据
它是Angular 各种视图的基础类,提供变更检测功能。 变更检测树会收集要检查的所有视图。 使用这些方法从树中添加或移除视图、初始化变更检测并显式地把这些视图标记为脏的,意思是它们变了、需要重新渲染。
它的五个方法:
- markForCheck(): 当视图使用 OnPush变更检测策略时,把该视图显式标记为已更改,以便它再次进行变更检测。
- detach(): 从变更检测树中分离开视图。 已分离的视图在重新附加上去之前不会被检查。 与 detectChanges() 结合使用,可以实现局部变更检测。
- detectChanges(): 检查该视图及其子视图。与 detach 结合使用可以实现局部变更检测。
- checkNoChanges(): 检查变更检测器及其子检测器,如果检测到任何更改,则抛出异常。
- reattach(): 把以前分离开的视图重新附加到变更检测树上。 视图会被默认附加到这棵树上。
使用:
- 引入ChangeDetectorRef模块
- 声明
- 使用
import { ChangeDetectionStrategy,ChangeDetectorRef } from '@angular/core';
@component({
selector: 'xxx',
exportAs: 'xxx',
changeDetection: ChangeDetectionStrategy.OnPush
})
constructor(
private: cdr:ChangeDetectorRef
){}
haha(){
this.cdr.markForCheck(); //需要时调用
}