OnChanges钩子
- 可变对象和不可变对象的区别
- 不可变对象给greeting赋值时改变的是它指向的内存的地址
- user仍然保持在被创建时的内存地址,修改的是这个内存地址的对象的内容,而它所指向的内存地址并没有改变
constructor() {
var greeting= "hello";
greeting = "hello w";
var user={name:"Tom"};
user.name="Jerry";
}
- 新建一个child组件,定义三个对象,以及调用OnChanges用来观察某些对象的值改变时组件中的操作
export class ChildComponent implements OnInit ,OnChanges{
@Input()
greeting:string;
@Input()
user:{name:string};
message:string = "初始化消息";
constructor() { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
console.log(JSON.stringify(changes,null,2));
}
}
<div class="child">
<h2>子组件</h2>
<div>问候语:{{greeting}}</div>
<div>姓名:{{user.name}}</div>
<div>消息:<input [(ngModel)]="message"></div>
</div>
- 在父组件中定义和子组件中相同的对象,观察数据的变化
export class AppComponent {
greeting:string="hello";
user:{name:string}={name:"Tom"};
constructor() {
}
}
<div class="parent">
<h2>父组件</h2>
<div>
问候语:<input type="text" [(ngModel)]="greeting">
</div>
<div>
姓名:<input type="text" [(ngModel)]="user.name">
</div>
<app-child [greeting]="greeting" [user]="user"></app-child>
</div>
-
初始化的时候会调用ngOnChanges,在父组件的输入框中改变greeting的值从hello变为helloa,angular的变更检测机制刷新不可变对象也就是greeting的值,因为greeting的值改变了,OnChanges方法被调用
- 修改姓名属性,将Tom修改成Toma,控制台中没有新的消息被打印,这是因为用户只是改变了可变对象user的属性,user对象自身的引用是没有改变的,所以ngOnChanges没有被调用,但是在子组件中的值仍然改变,这是由于angular的变更检测机制捕获了组件中每一个对象的属性变化
- 同样,改变子组件中的message属性也不会引起ngOnChanges的调用,因为它没有被@Input装饰器来注解,这意味它不是一个输入属性,而ngOnChanges只有在输入属性被改变时才会调用
angular的变更检测机制和DoCheck
- angular的变更检测机制是由zone.js来实现的(查看package.json),主要目的是保证组件的属性的变化和页面的变化是同步的,浏览器中发生的任何异步事件都会触发变更检测机制(点击按钮,输入数据,数据从服务器返回),zone.js会处理好所有的同步问题,当变更检测运行时,会检测组件模板上所有绑定关系,变更检测机制只是将组件属性的变化反应到模板上,不会改变组件属性的值
- angular实现两个变更检测策略
- Default策略:如果所有组件都使用Default策略,那么变更不管发生在哪个组件上,zone.js都会会检测整个组件数据
- OnPush策略:如果有一个组件声明自己的组件的策略为OnPush策略,那么只有组件的输入属性发生变化时,zone.js才会检测这个组件及其子组件
- 与变更检测相关的钩子函数DoCheck
- 定义一个oldUsername用来存放变更之前的name,定义一个changeDetected用来标记user.name属性是否发生变化默认为是,定义一个计数器noChangeCount,用来在数据没有变化时加1
export class ChildComponent implements OnInit ,OnChanges,DoCheck{
@Input()
greeting:string;
@Input()
user:{name:string};
message:string = "初始化消息";
oldUsername:string;
changeDetected:boolean=false;
noChangeCount:number=0;
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++;
console.log("DoCheck:user.name没有变化时ngDoCheck方法已经被调用"+this.noChangeCount+"次");
}
this.changeDetected=false;
}
}
- 在无任何操作时会执行DoCheck方法一次
- 光标反复切换到在name文本框中时也会执行DoCheck方法(修改greeting的值也会触发DoCheck方法)
- 改变name的数值,然后重新反复点击文本框,此时计数器被清零,重新开始计数
(ngDoCheck方法执行非常频繁,不一定修改了数据,鼠标移动到文本 框等操作也会执行DoCheck方法,只有很少的调用是你由于数据修改而触发的,因此这个方法的实现一定要非常高效非常轻量级否则很可能引起性能问题)