ホームページ >ウェブフロントエンド >jsチュートリアル >Angular での onPush 変更検出戦略をすばやく理解する
この記事では、Angular の onPush 変更検出戦略について詳しく説明します。お役に立てば幸いです。
デフォルトでは、Angular は変更検出に ChangeDetectionStrategy.Default
戦略を使用します。 。
デフォルトの戦略では、アプリケーションに関する事前の仮定が行われないため、ユーザー イベント、タイマー、XHR、Promise、およびその他のイベントによってアプリケーション内のデータが変更されるたびに、すべてのコンポーネントが変更検出を実行します。
これは、クリック イベントから Ajax 呼び出しから受信したデータまでのあらゆるイベントが変更検出をトリガーすることを意味します。
コンポーネントでゲッターを定義し、それをテンプレートで使用することで、これを簡単に確認できます。
@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() {} }
上記のコードを実行した後、ボタンをクリックするたびに。 Angular は変更検出ループを実行し、コンソールに 2 行の「ビューの確認」ログが表示されます。
この手法はダーティ チェックと呼ばれます。ビューを更新する必要があるかどうかを知るために、Angular は新しい値にアクセスし、それを古い値と比較して、ビューを更新する必要があるかどうかを判断する必要があります。
ここで、数千の式を含む大規模なアプリケーションがあり、Angular が各式をチェックすると、パフォーマンスの問題が発生する可能性があると想像してください。
では、コンポーネントをいつチェックするかを事前に Angular に伝える方法はあるのでしょうか?
コンポーネントの ChangeDetectionStrategy
を ChangeDetectionStrategy.OnPush
に設定できます。
これにより、コンポーネントが @inputs()
にのみ依存し、次の状況でのみチェックする必要があることが Angular に通知されます:
入力
参照変更onPush
変更検出検出戦略を設定することにより、Angular と契約して、不変オブジェクト (または後で紹介するオブザーバブル) の使用を強制します。
変更検出のコンテキストで不変オブジェクトを使用する利点は、参照が変更されたかどうかをチェックすることで、Angular がビューをチェックする必要があるかどうかを判断できることです。これは詳細な検査よりもはるかに簡単です。
オブジェクトを変更して結果を見てみましょう。
@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'; } }
現時点では、ボタンをクリックしてもログは表示されません。これは、Angular が次のように古い値と新しい値の参照を比較するためです。数値、ブール値、文字列、null、未定義はすべてプリミティブ型であることに注意してください。すべてのプリミティブ型は値によって渡されます。オブジェクト、配列、関数も値によって渡されますが、値は
参照アドレスのコピーです。 したがって、このコンポーネントで変更検出をトリガーするには、このオブジェクトへの参照を変更する必要があります。
/** Returns false in our case */ if( oldValue !== newValue ) { runChangeDetection(); }
オブジェクト参照を変更した後、ビューがチェックされ、新しい値が表示されていることがわかります。
2. コンポーネントまたはそのサブコンポーネントから発生するイベント
@Component({ template: ` <tooltip [config]="config"></tooltip> ` }) export class AppComponent { config = { position: 'top' }; onClick() { this.config = { position: 'bottom' } } }
ボタンをクリックすると、Angular は変更検出ループを実行し、ビューを更新します。
最初に説明したように、すべての非同期 API が変更検出をトリガーすると思うかもしれませんが、そうではありません。
このルールは DOM イベントにのみ適用されることがわかります。次の API は変更検出をトリガーしません:
@Component({ template: ` <button (click)="add()">Add</button> {{count}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class CounterComponent { count = 0; add() { this.count++; } }
まだ属性を更新していることに注意してください。そのため、次の変更検出プロセスでは、たとえば、ボタンをクリックすると、カウント値は 6 (5 1) になります。
3. 変更検出を明示的に実行する
最初の 1 つは
detectChanges() で、このコンポーネントとそのサブコンポーネントで変更検出を実行するように Angular に指示します。 <pre class="brush:js;toolbar:false;">@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(&#39;https://count.com&#39;).subscribe(res => {
this.count = res;
});
}
add() {
this.count++;
}</pre>
2 つ目は
で、アプリケーション全体に対して変更検出を実行するように Angular に指示します。 <pre class="brush:js;toolbar:false;">@Component({
selector: &#39;counter&#39;,
template: `{{count}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
count = 0;
constructor(private cdr: ChangeDetectorRef) {
setTimeout(() => {
this.count = 5;
this.cdr.detectChanges();
}, 1000);
}
}</pre>
3 番目は
で、変更検出はトリガーされません。代わりに、現在または次の変更検出ループで onPush が設定されているすべての祖先タグを検出します。 <pre class="brush:js;toolbar:false;">tick() {
try {
this._views.forEach((view) => view.detectChanges());
...
} catch (e) {
...
}
}</pre>
手動での変更検出の実行は「ハック」ではないことに注意してください。これは Angular の意図的な設計であり、非常に合理的な動作です (もちろん、合理的なシナリオの下では)。
Angular Async パイプ
async パイプは Observable または Promise をサブスクライブし、それが出力する最新の値を返します。
は監視可能な onPush コンポーネントです。 <pre class="brush:js;toolbar:false;">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;
}
}</pre><pre class="brush:js;toolbar:false;">@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);
}
}</pre>
ボタンをクリックしても、ビューの更新は表示されません。これは、上記の状況がいずれも発生していないため、Angular は現在の変更検出サイクルでコンポーネントをチェックしません。
现在,让我们加上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
译者:淼淼
更多编程相关知识,请访问:编程视频!!
以上がAngular での onPush 変更検出戦略をすばやく理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。