Event Bubbling の性能の問題
下記のAngularアプリケーションでの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を提出ください。
以上です。
どうもありがとうございました!
Top comments (0)