Artikel ini akan memberi anda pemahaman yang mendalam tentang strategi pengesanan perubahan onPush dalam Angular saya harap ia akan membantu anda!
Secara lalai, Angular menggunakan strategi ChangeDetectionStrategy.Default
untuk pengesanan perubahan.
Strategi lalai tidak membuat sebarang andaian tentang aplikasi terlebih dahulu Oleh itu, apabila peristiwa pengguna, pemasa, XHR, janji dan peristiwa lain menyebabkan data dalam aplikasi berubah, semua komponen akan Melakukan pengesanan perubahan.
Ini bermakna bahawa sebarang peristiwa daripada peristiwa klik kepada data yang diterima daripada panggilan ajax akan mencetuskan pengesanan perubahan.
Kita boleh melihatnya dengan mudah dengan mentakrifkan pengambil dalam komponen dan menggunakannya dalam templat:
@Component({ template: ` <h1>Hello {{name}}!</h1> {{runChangeDetection}} ` }) export class HelloComponent { @Input() name: string; get runChangeDetection() { console.log('Checking the view'); return true; } }
@Component({ template: ` <hello></hello> <button (click)="onClick()">Trigger change detection</button> ` }) export class AppComponent { onClick() {} }
Selepas melaksanakan kod di atas, bila-bila masa kita Apabila butang diklik . Angular akan melaksanakan gelung pengesanan perubahan, dan dalam konsol kita boleh melihat dua baris log "Menyemak paparan".
Teknik ini dipanggil pemeriksaan kotor. Untuk mengetahui sama ada paparan perlu dikemas kini, Angular perlu mengakses nilai baharu dan membandingkannya dengan nilai lama untuk menentukan sama ada paparan perlu dikemas kini.
Sekarang bayangkan jika terdapat aplikasi besar dengan beribu-ribu ungkapan, dan Angular menyemak setiap ungkapan, kita mungkin menghadapi masalah prestasi.
Jadi adakah cara untuk kami memberitahu Angular secara proaktif bila hendak menyemak komponen kami?
Kami boleh menetapkan ChangeDetectionStrategy
komponen kepada ChangeDetectionStrategy.OnPush
.
Ini akan memberitahu Angular bahawa komponen hanya bergantung pada @inputs()
nya dan hanya perlu diperiksa dalam situasi berikut:
Input
berubah Dengan menetapkan onPush
strategi pengesanan perubahan, kami membuat kontrak dengan Angular untuk menguatkuasakan penggunaan objek tidak boleh ubah (atau boleh diperhatikan seperti yang akan kami perkenalkan kemudian).
Kelebihan menggunakan objek tidak berubah dalam konteks pengesanan perubahan ialah Sudut boleh menentukan sama ada paparan perlu disemak dengan menyemak sama ada rujukan telah berubah. Ini akan menjadi lebih mudah daripada pemeriksaan mendalam.
Mari cuba ubah suai objek dan lihat hasilnya.
@Component({ selector: 'tooltip', template: ` <h1>{{config.position}}</h1> {{runChangeDetection}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class TooltipComponent { @Input() config; get runChangeDetection() { console.log('Checking the view'); return true; } }
@Component({ template: ` <tooltip [config]="config"></tooltip> ` }) export class AppComponent { config = { position: 'top' }; onClick() { this.config.position = 'bottom'; } }
Anda tidak dapat melihat sebarang log apabila anda mengklik butang pada masa ini Ini kerana Angular membandingkan rujukan nilai lama dan nilai baharu, serupa dengan:
./** Returns false in our case */ if( oldValue !== newValue ) { runChangeDetection(); }
Perlu dinyatakan bahawa nombor, boolean, rentetan, null dan undefined adalah semua jenis primitif. Semua jenis primitif diluluskan mengikut nilai Objek, tatasusunan dan fungsi juga diluluskan mengikut nilai, tetapi nilai tersebut adalah salinan alamat rujukan.
Jadi untuk mencetuskan pengesanan perubahan pada komponen ini, kita perlu menukar rujukan kepada objek ini.@Component({ template: ` <tooltip [config]="config"></tooltip> ` }) export class AppComponent { config = { position: 'top' }; onClick() { this.config = { position: 'bottom' } } }
@Component({ template: ` <button (click)="add()">Add</button> {{count}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class CounterComponent { count = 0; add() { this.count++; } }
@Component({ template: `...`, changeDetection: ChangeDetectionStrategy.OnPush }) export class CounterComponent { count = 0; constructor() { setTimeout(() => this.count = 5, 0); setInterval(() => this.count = 5, 100); Promise.resolve().then(() => this.count = 5); this.http.get('https://count.com').subscribe(res => { this.count = res; }); } add() { this.count++; }
untuk memberitahu Angular untuk melakukan pengesanan perubahan pada komponen ini dan subkomponennya. detectChanges()
@Component({ selector: 'counter', template: `{{count}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class CounterComponent { count = 0; constructor(private cdr: ChangeDetectorRef) { setTimeout(() => { this.count = 5; this.cdr.detectChanges(); }, 1000); } }
, yang memberitahu Angular untuk melakukan pengesanan perubahan pada keseluruhan aplikasi. ApplicationRef.tick()
tick() { try { this._views.forEach((view) => view.detectChanges()); ... } catch (e) { ... } }
, yang tidak mencetuskan pengesanan perubahan. Sebaliknya, ia akan mengesan semua teg nenek moyang dengan set onPush dalam kitaran pengesanan perubahan semasa atau seterusnya. markForCheck()
markForCheck(): void { markParentViewsForCheck(this._view); } 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; } }
Paip akan melanggan Observable atau Promise dan mengembalikan nilai terkini yang dikeluarkannya. async
boleh diperhatikan. input()
@Component({ template: ` <button (click)="add()">Add</button> <app-list [items$]="items$"></app-list> ` }) export class AppComponent { items = []; items$ = new BehaviorSubject(this.items); add() { this.items.push({ title: Math.random() }) this.items$.next(this.items); } }
@Component({ template: ` <div *ngFor="let item of _items ; ">{{item.title}}</div> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class ListComponent implements OnInit { @Input() items: Observable<Item>; _items: Item[]; ngOnInit() { this.items.subscribe(items => { this._items = items; }); } }
现在,让我们加上async
pipe试试。
@Component({ template: ` <div *ngFor="let item of items | async">{{item.title}}</div> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class ListComponent implements OnInit { @Input() items; }
现在可以看到当我们点击按钮时,视图也更新了。原因是当新的值被发射出来时,async
pipe将该组件标记为发生了更改需要检查。我们可以在源码中看到:
private _updateLatestValue(async: any, value: Object): void { if (async === this._obj) { this._latestValue = value; this._ref.markForCheck(); } }
Angular为我们调用markForCheck()
,所以我们能看到视图更新了即使input的引用没有发生改变。
如果一个组件仅仅依赖于它的input属性,并且input属性是observable,那么这个组件只有在它的input属性发射一个事件的时候才会发生改变。
Quick tip:对外部暴露你的subject是不值得提倡的,总是使用asObservable()
方法来暴露该observable。
@Component({ selector: 'app-tabs', template: `<ng-content></ng-content>` }) export class TabsComponent implements OnInit { @ContentChild(TabComponent) tab: TabComponent; ngAfterContentInit() { setTimeout(() => { this.tab.content = 'Content'; }, 3000); } }
@Component({ selector: 'app-tab', template: `{{content}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class TabComponent { @Input() content; }
<app-tabs> <app-tab></app-tab> </app-tabs>
也许你会以为3秒后Angular将会使用新的内容更新tab组件。
毕竟,我们更新来onPush组件的input引用,这将会触发变更检测不是吗?
然而,在这种情况下,它并不生效。Angular不知道我们正在更新tab组件的input属性,在模板中定义input()
是让Angular知道应在变更检测循环中检查此属性的唯一途径。
例如:
<app-tabs> <app-tab [content]="content"></app-tab> </app-tabs>
因为当我们明确的在模板中定义了input()
,Angular会创建一个叫updateRenderer()
的方法,它会在每个变更检测循环中都对content的值进行追踪。
在这种情况下简单的解决办法使用setter然后调用markForCheck()
。
@Component({ selector: 'app-tab', template: ` {{_content}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class TabComponent { _content; @Input() set content(value) { this._content = value; this.cdr.markForCheck(); } constructor(private cdr: ChangeDetectorRef) {} }
在理解了onPush
的强大之后,我们来利用它创造一个更高性能的应用。onPush组件越多,Angular需要执行的检查就越少。让我们看看你一个真是的例子:
我们又一个todos
组件,它有一个todos作为input()。
@Component({ selector: 'app-todos', template: ` <div *ngFor="let todo of todos"> {{todo.title}} - {{runChangeDetection}} </div> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class TodosComponent { @Input() todos; get runChangeDetection() { console.log('TodosComponent - Checking the view'); return true; } }
@Component({ template: ` <button (click)="add()">Add</button> <app-todos [todos]="todos"></app-todos> ` }) export class AppComponent { todos = [{ title: 'One' }, { title: 'Two' }]; add() { this.todos = [...this.todos, { title: 'Three' }]; } }
上述方法的缺点是,当我们单击添加按钮时,即使之前的数据没有任何更改,Angular也需要检查每个todo。因此第一次单击后,控制台中将显示三个日志。
在上面的示例中,只有一个表达式需要检查,但是想象一下如果是一个有多个绑定(ngIf,ngClass,表达式等)的真实组件,这将会非常耗性能。
我们白白的执行了变更检测!
更高效的方法是创建一个todo组件并将其变更检测策略定义为onPush。例如:
@Component({ selector: 'app-todos', template: ` <app-todo [todo]="todo" *ngFor="let todo of todos"></app-todo> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class TodosComponent { @Input() todos; } @Component({ selector: 'app-todo', template: `{{todo.title}} {{runChangeDetection}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class TodoComponent { @Input() todo; get runChangeDetection() { console.log('TodoComponent - Checking the view'); return true; } }
现在,当我们单击添加按钮时,控制台中只会看到一个日志,因为其他的todo组件的input均未更改,因此不会去检查其视图。
并且,通过创建更小粒度的组件,我们的代码变得更具可读性和可重用性。
原文链接: https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4
原文作者:Netanel Basal
译者:淼淼
更多编程相关知识,请访问:编程视频!!
Atas ialah kandungan terperinci Fahami dengan cepat strategi pengesanan perubahan onPush dalam Angular. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!