注:我用的angular 8
一個angular應用是由元件樹組成的,changeDetection是其中比較深的部分,我也不懂哈。
angular中changeDetection中的政策有這樣的描述:
總而言之,對于一個元件而言,2中changeDetection政策,預設的沒啥好說的,主要說一下OnPush的情況。
如果子元件的屬性的變化由輸入屬性決定,那麼這個時候就可以啟用OnPush這種變更檢測政策,這樣輸入屬性不變的時候就不用檢測了,省時省力。
1、 輸入屬性為非對象的時候
index元件:
@Component({
selector: 'app-index',
template: '<app-abc [shuru]="shuru"></app-abc>',
})
export class IndexComponent {
shuru = 1;
constructor() {
setInterval(() => {
this.shuru++;
}, 1111);
}
}
abc元件:
@Component({
selector: 'app-abc',
template: ' 輸入是:{{shuru}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges {
@Input()
shuru = 0;
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
跑一下看結果:
這個實在沒啥好說的哈!
2、輸入屬性是一個對象的時候
修改index元件代碼如下:
@Component({
selector: 'app-index',
template: '<app-abc [shuru]="shuru"></app-abc>',
})
export class IndexComponent {
shuru = {
shuru: 1
};
constructor() {
setInterval(() => {
this.shuru.shuru++;
}, 1111);
}
}
修改abc元件如下:
@Component({
selector: 'app-abc',
template: ' 輸入是:{{shuru.shuru}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges {
@Input()
shuru = {shuru: 0};
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
再跑一下:
為什麼abc元件裡面沒變化呢,因為再index元件中輸入屬性shuru沒有變化,我們改變的隻是shuru.shuru,angular比較的是shuru的reference
3、輸入屬性與immutable objects
2沒有生效的,是因為我修改的是shuru這個對象,而不是shuru的引用;那麼作為輸入屬性傳入的每個對象本身如果是不可修改的,如果我想修改shuru的時候,重新指派成另外一個對象就可以了。
對于2中的情況,如果我們想讓他生效,這麼做就好了:
index元件稍微修改下:
this.shuru = {
shuru : ++this.shuru.shuru
};
// this.shuru.shuru++;
4、OnPush與事件
除了上述3能捕獲到變更檢測,還有一種詭異的情況:
index元件的代碼和2保持一直,修改abc元件的代碼如下:
@Component({
selector: 'app-abc',
template: ' 輸入是:{{shuru.shuru}} <button (click)="click()">我是按鈕</button>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges {
@Input()
shuru = {shuru: 0};
// shuru: Observable<any>;
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
click() {
console.log('電力');
}
}
跑一下:
對此,我隻能解釋成,事件的觸發,導緻元件進行了變更檢測;那麼,我也可以自己在元件abc中手動做變更檢測,是以可以像下面這樣做:
5、OnPush與ngDoCheck、ChangeDetectorRef、markForCheck
When should you use ngDoCheck?
Use ngDoCheck when you want to capture changes that Angular otherwise doesn’t.
For example, if a binding references remains unchanged after a click event, ngOnChanges won’t run but ngDoCheck will.
在2的基礎之上進行。。。
雖然這時候ngOnChanges不執行了,但是ngDoCheck執行啊!!!
修改元件abc的代碼如下:
@Component({
selector: 'app-abc',
template: ' 輸入是:{{shuru.shuru}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges, DoCheck {
@Input()
shuru = {shuru: 0};
constructor(private changeDetectorRef: ChangeDetectorRef,
) {
/*
Detaches this view from the change-detection tree.
A detached view is not checked until it is reattached.
Use in combination with detectChanges() to implement local change detection checks.
*/
this.changeDetectorRef.detach(); // 如果detach, 那麼markForCheck就不起作用了
}
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
ngDoCheck() {
console.log('ngDoCheck', this.shuru);
// this.changeDetectorRef.markForCheck(); // 不detach的時候,這個也可以
this.changeDetectorRef.detectChanges(); // Checks this view and its children.
}
}
6、當輸入屬性是service時候
居然還能這樣!
先搞個服務處理:
@Injectable({
providedIn: 'root'
})
export class ObsService {
private messageSource = new BehaviorSubject(1);
comeOneData = this.messageSource.asObservable();
changeData(message: any) {
this.messageSource.next(message);
}
}
新的index元件如下:
@Component({
selector: 'app-index',
template: '<app-abc [shuru]="obs$"></app-abc>',
})
export class IndexComponent {
shuru = 1;
constructor(private obs$: ObsService) {
setInterval(() => {
this.obs$.changeData(this.shuru++);
}, 1111);
}
}
新的abc元件如下:
@Component({
selector: 'app-abc',
template: ' 輸入是:{{shuru["comeOneData"]| async | json}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges {
@Input()
shuru: Observable<any>;
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
7、當輸入屬性是Observable的時候
這個其實和6有點類似的。
index元件是這樣的:
@Component({
selector: 'app-index',
template: '<app-abc [shuru]="shuru"></app-abc>',
})
export class IndexComponent {
observer;
num = 0;
shuru = Observable.create((observer) => {
this.observer = observer;
});
constructor() {
setInterval(() => {
this.observer.next(++this.num);
}, 1111);
}
}
7.1 訂閱方式
這個方法得多寫幾行代碼。。。在abc元件中subscribe輸入的Observable,取出來值之後自己做變更檢測插入到模闆中:
abc元件如下:
@Component({
selector: 'app-abc',
template: ' 輸入是:{{num}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges, OnInit {
@Input()
shuru: Observable<any>;
num;
constructor(private changeDetectorRef: ChangeDetectorRef,
) {
}
ngOnInit() {
this.shuru.subscribe((res) => {
console.log('subscribe',res);
this.num = res;
this.changeDetectorRef.detectChanges();
});
}
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
7.2 管道
abc元件如下,少些好多代碼。不用手動做變更檢測了
@Component({
selector: 'app-abc',
template: ' 輸入是:{{shuru|async}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AbcComponent implements OnChanges {
@Input()
shuru: Observable<any>;
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
參考文獻
https://angular.io/api/core/ChangeDetectorRef
https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c
https://blog.angular-university.io/onpush-change-detection-how-it-works/
https://blog.angularindepth.com/if-you-think-ngdocheck-means-your-component-is-being-checked-read-this-article-36ce63a3f3e5
https://www.stackchief.com/blog/ngDoCheck Example | Angular