この記事がAngular #2 Advent Calendar 2019
23日目の記事です。
Event Bubbling の性能の問題
下記のHTMLコードを見ましょう。
<div (click)="doSomething()">
<button (click)="doAnotherThing()">Button</button>
</div>
Event Bubbling のため、DivのなかのButtonをクリックしたら、両方のEvent ListenerがTriggerされます。この2つのEvent Listenerが全部Change Detectionを実行します。実際がこのようなケースでChange Detectionを一回だけ実行したいです。特にこのようなケースがAngular MaterialとかUI ライブラリで結構普通のケースですので、性能改善したいです。
実行順番
まず上記のケースでEvent ListenerとChange Detectionの実行順番を見ましょう。
export class AppComponent {
doSomething() { // event listener for parent div
console.log('event listener for parent div');
}
doAnotherThing() { // event listener for inner button
console.log('event listener for inner button');
}
}
そして、Zone.jsがEvent ListenerをMonkey patchされましたので、下記のような感じのコードになりました。
function eventHandler(...) {
try {
realHandler(...); // doSomething あるいはdoAnotherThingのDelegate
} finally {
applicationRef.tick();
}
}
そしたら、Buttonをクリックするとき、実行の順番が下記のようになりました。
event listener for inner button
applicationRef.tick
event listener for parent div
applicationRef.tick
実際ほしいのは
event listener for inner button
event listener for parent div
applicationRef.tick
です。
解決方法
Change Detectionを同期ではなく、非同期で実行することです。
isChangeDetectionScheduled = false;
function eventHandler(...) {
try {
realHandler(...); // doSomething あるいはdoAnotherThingのDelegate
} finally {
if (isChangeDetectionScheduled) {
return;
}
isChangeDetectionScheduled = true;
requestAnimationFrame(() => {
isChangeDetectionScheduled = false;
applicationRef.tick();
});
}
}
のような感じのコードでChange DetectionをrequestAnimationFrameのSchedulerで実行させ、そしてもしScheduleされたTaskがあったら、スキップして、なかったら、Scheduleするということです。
設定方法
bootstrapModuleのとき、かきのOptionを設定することができます。
platformBrowserDynamic().bootstrapModule(AppModule, {ngZoneEventCoalescing: true})
副作用
このオプションをTrueにしたら、もともと同期のChange Detectionが非同期になりました、普通のアプリケーションには影響がないですが、Google 内部でのEdge Caseで同期のChange Detectionが求めるテストケースがあるらしくて、それ以外が特に既存のアプリケーションには影響ないはずです。
この機能がすでに最新バージョンで使えますので、なにか問題が発見されたら、ぜひAngular RepoにIssueを提出ください。
以上です。
どうもありがとうございました!