更新于2021.11.17
version:12+
Angular变更检测分为默认的变更检测策略(CheckAlways)和OnPush变更检测策略(CheckOnce),对于变更检测,网上的资源只是片面的对其进行解释,下面对变更策略进行详细的分析(Angular的变更检测是以深度优先为基础,遇到兄弟组件优先兄弟组件,然后对组件树进行单向的检测并刷新页面数据).
对于Angular而言每个组件都有一个对应的视图View
,视图与组件有直接的关联,而每个视图都有一个通过nodes属性链接到其子视图的链接,因此可以对子视图执行操作
视图状态
对于每个视图而言都有一个状态State
,当视图状态ChecksEnabled
为false
或处于Errored/Destroyed
状态下,将跳过更改检测.默认视图的状态ChecksEnabled
为true
,但当处于OnPush策略下时,视图状态将ChecksEnabled
将变为false
,OnPus策略会影响当前组件及其所有子组件;只有当input属性发生变化时,才会重新对视图进行变更检测//OnPush策略下更改视图状态
if (view.def.flags & ViewFlags.OnPush) {
view.state &= ~ViewState.ChecksEnabled;
}
//视图状态 |
而Angulr将这一概念称为ViewRef
,它封装了基础组件视图,并且有一个我们所熟知的名称 detectChanges
,通过构造函数我们可以了解到detectChanges
下有以下几个方法
export class AppComponent { |
变更检测操作
下面就是我们整个检测操作流程
- sets ViewState.firstCheck to true if a view is checked for the first time and to false if it was already checked before
- checks and updates input properties on a child component/directive instance
- updates child view change detection state (part of change detection strategy implementation)
- runs change detection for the embedded views (repeats the steps in the list)
- calls OnChanges lifecycle hook on a child component if bindings changed
- calls OnInit and ngDoCheck on a child component (OnInit is called only during first check)
- updates ContentChildren query list on a child view component instance
- calls AfterContentInit and AfterContentChecked lifecycle hooks on child component instance (AfterContentInit is called only during first check)
- updates DOM interpolations for the current view if properties on current view component instance changed
- runs change detection for a child view (repeats the steps in this list)
- updates ViewChildren query list on the current view component instance
- calls AfterViewInit and AfterViewChecked lifecycle hooks on child component instance (AfterViewInit is called only during first check)
- disables checks for the current view (part of change detection strategy implementation)
通过对流程的梳理,我们知道视图的状态在我们整个变更检测中起着重要的所有.
假如有A>>B>>C三个组件按照这个层级结构,他们钩子的调用顺序如下
A: AfterContentInit |
探索
回头我们再来看我们所熟知的一些知识点,就发现所有这些方法的调用都只是在更改我们视图的状态,
detach()
this._view.state &= ~ViewState.Attached;
reattach()
this._view.state |= ViewState.Attached;
markForCheck():
export function markParentViewsForCheck(view: ViewData) {
let currView: ViewData|null = view;
while (currView) {
if (currView.def.flags & ViewFlags.OnPush) {
currView.state |= ViewState.ChecksEnabled;
}
currView = currView.viewContainerParent || currView.parent;
}
}detectChanges():这里与detach()可以局部刷新的原理就是首先将组件脱离视图,避免脏检查,同时在需要的地方调用detectChanges(),翻看源码发现detectChanges调用了视图的基础方法
checkAndUpdateView()
来更新数据- checkNoChanges()开发时使用,不过多解释
默认检测策略下的触发时机
- 事件:页面内的一些列事件click、submit、mouse、down等等
- 组件@Input()参数的变化(值引用)
- setTimeout()、setInterval()
- Observable
OnPush策略下的触发时机
- 事件:页面内的一些列事件click、submit、mouse、down等等
- Observable 但是需要设置 Async pipe
- 组件@Input()参数的变化(值引用)
- 手动使用
ChangeDetectorRef.detectChanges()、ChangeDetectorRef.markForCheck()、ApplicationRef.tick()
方法
在Angular源码中看到变化检测对象ChangeDetectorStatus
有如下几种状态:
CheckOnce:表示只检查一次,调用detectChanges之后状态将会变为Checked
Checked:表示在状态变为CheckOnce之前会跳过所有检测
CheckAlways:表示总是接受变化检测,每次调用detectChanges后状态还是CheckAlways
Detached:代表变化检测对象脱离了变化检测对象树,不再进行变化检测
Errored:表述变化测试对象发生错误,变化检测实效
Destroyed:表示变化检测对象已经被销毁
应用
- 将组件设置为
OnPush
模式、markForCheck()
和非纯管道async
组合的形式优化性能 - 将组件脱离文档流
detach()
,并调用detectChanges()
来进行局部的变更检测 局部代码通过
zone.js
来实现在合适的时机进行变更检测this.ngZone.runOutsideAngular(() => {
this._sub = Observable.timer(1000, 1000)
.subscribe(i => this.ngZone.run(() => {
this.content = "Loaded! " + i;
}));
});