前置笔记:
ChangeDetectionStrategy.Default 默认模式
ChangeDetectionStrategy.OnPush OnPush模式 (NGZORRO使用的优化手段)
简单来说:默认模式下,一旦检测到父级组建的变化,更新所有的子组件。
这边举个例子:
@Component({
template: `<h1>I am { { data.name } } and I live in { { data.address } } </h1>
<cd-child [data]="data"></cd-child>
<button (click)="changeInfo()">Change Info</button>`
})
export class CDParentComponent {
data: any = {
name: 'James',
address: 'ShangHai',
contact: {
email: 'XXX@gmail.com',
phone: '1234567890'
}
};
changeInfo() {
this.data.contact.email = 'update@gmail.com';
this.data.contact.phone = '00000000';
this.data.name = 'Kobe';
}
}
其子组件:
@Component({
selector: "cd-child",
template: `<h3>here is email in the child: { { data.contact.email } } </h3>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CDChildComponent implements OnChanges {
@Input() data: any;
ngOnChanges() {
console.log('data has been changed: ' + this.data.name + ' ' + this.data.address);
}
}
我们点击 Change Info 按钮,不会触发 CDChildComponent 中的变化检测,页面 email 也不会有变化。
以下四种情况还是可以触发该组件的变化检测:
组件的
@Input
引用发生变化。组件的 DOM 事件,包括它子组件的 DOM 事件,比如 click、submit、mouse down。
Observable 订阅事件,同时设置 Async pipe。
利用以下方式手动触发变化检测:
- ChangeDetectorRef.detectChanges
- ChangeDetectorRef.markForCheck()
- ApplicationRef.tick()
重点说说@input引用的变化
好好想想,自己是不是更新子组件时候写过这种代码,知不知道这么写的原因啊! :
fuckArray = [...anotherFcukArray];
必须是 @Input 的引用发生改变才会触发变化检测,并且仅限于 @Input 的变化检测,在 OnPush 策略下,会触发组件的变化检测。在这里先解释一下 JS 中的数据类型,在 JS 中有七种数据类型,其中包括六中原始类型(primitive values)和 Object。
六种原始类型分别为:Boolean、Null、Undefined、Number、String、Symbol (ECMAScript 6 新定义)。
除了 Object 以外的所有类型(即原始类型)都是不可变的(Immutable),是通过值传递的,每次对它们的改动都会在内存里生成一个新的值。而 Object 是通过引用传递的,每次对 Object 改动,引用不会改变。
在上面的示例代码CDParentComponent中的changeInfo
方法如下:
changeInfo() {
this.data.contact.email = 'update@gmail.com';
this.data.contact.phone = '00000000';
this.data.name = 'BigFool';
}
data 是一个对象,在changeInfo
方法里通过如上方式改变 email 的值。同时在 CDChildComponent 设置了 OnPush,虽然@Input data
的属性 eamil 发生变化但是 data 对象的引用并没有改变,并不会触发 CDChildComponent 中的变化检测,页面的 eamil 也不会发生变化。
如果把 CDParentComponent 中的changeInfo
方法改成下面这样:
changeInfo() {
this.data = {
name: 'Curry', address: 'ShangHai',
contact: {
email: 'update@gmail.com',
phone: '1234567890'
}
};
}
这时候点击 Change Info 按钮,触发了变化检测,页面的 email 被更新了:
这种方式在改变 data 对象 email 值同时也改变了对象的引用。这时组件的 @Input 引用发生变化,虽然加了 OnPush 但 @Input 的变化检测还是会被触发。
再重点说说手动触发更新
在 OnPush 策略下,手动调用这三种方式会触发变化检测:
- ChangeDetectorRef.detectChanges
- ChangeDetectorRef.markForCheck()
- ApplicationRef.tick()
要 依赖 注入 进去 才可以!不要像弱智一样粘贴复制一下方法就仍到代码里面!
@Component({
selector: "cd-child",
template: `<h3>here is email in the child: { { data.contact.email } } </h3>
<h3>here is the counter triggered manually in the child: { { counter } } </h3>
<div style="margin-bottom:10px;">
<button (click)="changeCounter()">change child counter</button>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CDChildComponent implements OnInit, OnChanges {
@Input() data: any;
counter: number = 1;
count$: Observable<number>;
constructor(
// 看这里 傻逼
private cd: ChangeDetectorRef
) { }
ngOnInit() {
setInterval(() => {
this.counter = this.counter + 5;
// 还有这里 傻逼
this.cd.detectChanges();
}, 1000);
}
ngOnChanges() {
console.log('data has been changed: ' + this.data.name + ' ' + this.data.address);
}
}
ChangeDetectorRef.markForCheck()效果跟 detectChanges 是一样的,只不过 detectChanges 会立马触发当前组件和它子组件变化检测。markForCheck 并不会立马触发变化检测,而是标记需要被变化检测,在当前或下一轮的变化检测中被触发。
ApplicationRef.tick() 触发整个应用的组件树从上到下执行变化检测。