碎碎念:知识点梳理归纳,如果有什么不对的感谢大家指正一起学习!
ngDoCheck
一、变化监测
在ngDoCheck钩子之前需要了解一下
- 变更检测机制:将组件属性的改变反应到模板上
- 异步事件会导致组件数据变化,NgZone会在适当的时机去检验对象的值是否被改动,然后驱动angular的变化监测机制执行
- 每个组件都有一个变化监测类的实例,实例提供方法手动管理变化监测。可以给组件做标记,通知angular仅仅监测该组件所在路径上的组件
数据改变了----> NGZone获取了 ----> 通知angular ----> 执行变化检测
二、ngDoCheck实例
- 用于变化监测,该钩子方法在每次变化监测发生时被调用
- angular变更检测机制由NgZone.实现的。目的:组件属性的变化和页面的变化是同步的
- 每一个变化监测周期内,不管数据值是否发生了变化,ngDoCheck都会被调用(例鼠标移动会触发mousemove事件),会被频繁触发调用
例:在子组件添加ngDoCheck方法
// html
<div class="bg">
<h2>我是子组件</h2>
<p>问候语:{{greetings}}</p>
<p>姓名:{{user.name}}</p>
</div>
// ts
export class TestComponent implements OnInit, DoCheck {
@Input() greetings: string;
@Input() user: { name: string };
noChangeCount: number = 0;
oldUserName: string;
changeDetected: boolean = false;
constructor() {}
ngDoCheck(): void {
if (this.user.name !== this.oldUserName) {
this.changeDetected = true;
console.log("DoCheck:user.name从" + this.oldUserName + "变为" + this.user.name);
this.oldUserName = this.user.name;
}
if (this.changeDetected) {
this.noChangeCount = 0;
} else {
this.noChangeCount = this.noChangeCount + 1;
console.log("DoCheck:user.name值没发生变化时,doCheck方法已经被调用" + this.noChangeCount + "次");
}
this.changeDetected = false;
}
}
逻辑:
- 判断当前user.name值是否发生了改变
- 如果发生改变就将变量changeDetected为true(打印)
- 然后将当前的用户名赋值给变量oldUserName
- 判断如果user.name值没有发生改变,添加doCheck被调用次数
效果:
总结:
- 页面初次加载时也会触发钩子
- 仅仅只是鼠标的点击也会频繁触发钩子方法并记录触发次数(并没有改变对象值)
- 每次数据的改变会把清空计数器
- 不改变name值doCheck方法也会被调用
三、Angular的两种变化检测策略
3.1 Default策略
是Angular默认的变化检测策略,也就是脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查。
触发条件:
- 用户输入操作,比如点击,提交等
- 请求服务端数据(XHR)
- 定时事件,比如setTimeout,setInterval
优点:
每一次有异步事件发生,Angular都会触发变更检测(脏检查),从根组件开始遍历其子组件,对每一个组件都进行变更检测,对dom进行更新
缺点:
有很多组件状态(state)没有发生变化,无需进行变更检测,进行没有必要的变更检测,如果你的应用程序中组件越多,性能问题会越来越明显
3.2 OnPush策略
-
就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。这种策略检查不彻底,但效率高。
-
相比Default只需要在@Component装饰器中加上一句:
changeDetection:ChangeDetectionStrategy.OnPush
触发条件:
Input()
优点:
组件的变更检测(脏检查)完全依赖于组件的输入(@Input),只要输入值不变,就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升
缺点:
必须保证输入(@Input)是不可变的,手动进行变化检测
手动变化检测
通过引用变化检测对象ChangeDetectorRef
ChangeDetectorRef介绍
- markForCheck() :使用于子组件,将该子组件到根组件之间的路径标记起来,通知angular检测器下次变化检测时一定检查此路径上的组件;(标记为脏的,需要重新渲染)。
- detach() :将组件的检测器从检测器树中脱离,不再受检测机制的控制,除非手动调用 reattach() 方法。
- reattach() - 脱离的检测器重新链接到检测器树上,使得该组件及其子组件都能执行变化检测
- detectChanges() - 从该组件到各个子组件执行一次变化检测 检查该视图及其子视图。与 detach 结合使用可以实现局部变更检测。
四、两种策略实例
4.1 在default策略下
实现逻辑:
- 点击“改变明星属性”按钮,可以看到子组件中原本为“周杰伦”被改变成了“吴彦祖”
- 点击“改变明星对象”按钮,可以看到子组件中原本为“吴彦祖”被改变成了“刘德华”
代码:
父组件
// html
<div class="bg">
<h1>变更检测策略</h1>
<button type="button" (click)="changeStar()"> 改变明星属性
</button>
<button type="button" (click)="changeStarObject()">
改变明星对象
</button>
<app-movie [title]="title" [star]="star"></app-movie>
</div>
// ts
@Component({
selector: 'app-prouct-one',
templateUrl: './prouct-one.component.html',
styleUrls: ['./prouct-one.component.scss']
})
export class ProuctOneComponent implements {
title: string = 'default 策略';
star: Star = new Star('周', '杰伦');
changeStar() {
this.star.firstName = '吴';
this.star.lastName = '彦祖';
}
changeStarObject() {
this.star = new Star('刘', '德华');
}
}
export class Star {
constructor(
public firstName: string,
public lastName: string
) { }
}
子组件
// html
<div class="bg">
<h3>{{ title }}</h3>
<p>
<label >Star值:</label>
<span>{{star.firstName}}{{star.lastName}}</span>
</p>
</div>
// ts
@Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.scss'],
})
export class MovieComponent implements {
@Input() title: string;
@Input() star;
constructor(
) { }
}
效果:
总结:
- default策略下,父组件的改变会触发所有的变更检测,子组件的值被改变反映到视图上
- 便于区分粉色区是父组件,淡橙色是子组件
4.2在OnPush策略下(1)-- 事件
代码:
父组件的代码不变。下面是子组件的代码
// html
<div class="bg">
<h3>{{ title }}</h3>
<p>
<label >Star值:</label>
<span>{{star.firstName}}{{star.lastName}}</span>
</p>
<p (click)="change()">点击我</p>
</div>
// ts
import { Component, OnInit, Input, DoCheck, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MovieComponent implements OnInit, DoCheck, OnChanges {
@Input() title: string;
@Input() star;
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
console.log("changes", JSON.stringify(changes, null, 2))
}
change() {
console.log("我点击了")
}
}
效果:
总结:
- 点击“改变明星属性”按钮,可以看到子组件中原本为“周杰伦”没有被改变。ngchange中也没有监听到事件。因为,改变的只是对象的值而不是对象的引用 。在该策略下就认为没有被改变,不会进行变化监测。
- 点击“改变明星对象”按钮,可以看到子组件中原本为“周杰伦”被改变成了“刘德华”。因为这个改变了对象的引用。
- 当触发了“点击我”,发现“周杰伦”,变成了“吴彦祖”。因为在子组件异步的事件会触发变化监测机制,通知anguar来更新数据。
- 在这个过程中可以观察onchange的打印,在“改变明星属性”时虽然没有更新到视图也没有触发onchange事件,但是在“改变明星对象”触发后可以看到控制台的打印,"周杰伦"已经被改变为“吴彦祖”。
- 截图中,“default策略”忘记改了…这里是“OnPush策略”哈
4.3在OnPush策略下(2)-- 手动监测
代码:
只需要稍微的修改一下子组件的ts代码
// ts
import { Component, OnInit, Input, DoCheck, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MovieComponent implements OnInit, DoCheck, OnChanges {
@Input() title: string;
@Input() star;
constructor(
private cdr: ChangeDetectorRef
) { }
ngOnChanges(changes: SimpleChanges): void {
console.log("changes", JSON.stringify(changes, null, 2))
}
ngDoCheck(): void {
this.cdr.markForCheck();
}
change() {
console.log("我点击了")
}
}
效果:
总结:
- 在DoCheck钩子中我们执行手动监测的方法,这样不用异步的事件angular也会进行变化监测
相关链接: