
このドキュメントには、角度アプリケーションのパフォーマンスを向上させるのに役立つプラクティスのリストが含まれています。 「Angular Performance Checklist」では、サーバー側のアプリケーションのレンダリングやバンドルのバンドルなど、フレームワークによって実行される変更検出の最適化まで、さまざまなトピックをカバーしています。
ドキュメントは2つの主要なセクションに分かれています。
一部のプラクティスは両方のカテゴリに影響を与えるため、わずかな交差点がある可能性がありますが、ユースケースの違いとその意味について明示的に言及されます。
ほとんどのサブセクションは、特定の実践に関連するツールをリストし、開発フローを自動化することでより効率的になります。
ほとんどのプラクティスは、HTTP/1.1とHTTP/2の両方で有効であることに注意してください。例外を作成するプラクティスは、適用できるプロトコルのバージョンを指定することにより言及されます。
enableProdModeを使用しますChangeDetectionStrategy.OnPush*ngForディレクティブtrackByオプションを使用しますこのセクションのいくつかのツールはまだ開発中であり、変更される可能性があります。 Angular Coreチームは、可能な限りアプリケーションのビルドプロセスの自動化に取り組んでいるため、多くのことが透過的に発生します。
バンドリングは、ユーザーが要求するアプリケーションを配信するために、ブラウザが実行する必要がある要求の数を減らすことを目的とした標準的な慣行です。本質的に、バンドラーは入力としてエントリポイントのリストを受け取り、1つ以上のバンドルを生成します。このようにして、ブラウザは、個々のリソースを個別にリクエストする代わりに、いくつかのリクエストのみを実行することでアプリケーション全体を取得できます。
アプリケーションが成長するにつれて、すべてを1つの大きなバンドルにバンドルすることは再び逆効果になります。 Webpackを使用してコード分割技術を探索します。
サーバープッシュ機能のため、追加のHTTP要求はHTTP/2の懸念事項ではありません。
ツーリング
アプリケーションを効率的にバンドルできるようにするツールは次のとおりです。
リソース
これらのプラクティスにより、アプリケーションのペイロードを減らすことにより、帯域幅の消費を最小限に抑えることができます。
ツーリング
リソース
Whitespaceの文字( s regexと一致する文字)は表示されませんが、ネットワークを介して転送されるバイトで表されます。テンプレートから最小限に縮小する場合、それぞれAOTコードのバンドルサイズをさらにドロップできるようになります。
ありがたいことに、これを手動で行う必要はありません。 ComponentMetadataインターフェイスは、デフォルトではfalse意味を持つプロパティpreserveWhitespacesを提供します。デフォルトでは、Angularコンパイラがホワイトスパースをトリミングしてアプリケーションのサイズをさらに削減します。プロパティをtrue Angularに設定した場合に備えて、Whitespaceが保存されます。
アプリケーションの最終バージョンでは、通常、Angularおよび/またはサードパーティライブラリによって提供されるコード全体を使用しません。 ES2015モジュールの静的な性質のおかげで、アプリで参照されていないコードを取り除くことができます。
例
// foo.js
export foo = ( ) => 'foo' ;
export bar = ( ) => 'bar' ;
// app.js
import { foo } from './foo' ;
console . log ( foo ( ) ) ;ツリーシェイクとバンドルapp.jsが取得されたら:
let foo = ( ) => 'foo' ;
console . log ( foo ( ) ) ;これは、未使用のエクスポートbar最終バンドルに含まれないことを意味します。
ツーリング
注: GCCはまだexport *をサポートしていません。これは、「バレル」パターンの使用量が多いため、角度アプリケーションを構築するために不可欠です。
リソース
Angularバージョン6のリリース以来、Angularチームはサービスを樹木を抑えることができる新しい機能を提供しました。つまり、他のサービスやコンポーネントで使用されていない限り、サービスは最終バンドルに含まれません。これは、バンドルから未使用のコードを削除することにより、バンドルサイズを縮小するのに役立ちます。
@Injectable()デコレータを使用するときにサービスを初期化する場所を定義するために、 providedIn属性を使用して、サービスをシェービング可能にすることができます。次に、次のように、 NgModule宣言のproviders属性とそのインポートステートメントから削除する必要があります。
前に:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
import { MyService } from './app.service'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ MyService ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( )
export class MyService { }後:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( {
providedIn : 'root'
} )
export class MyService { } MyServiceがコンポーネント/サービスに注入されていない場合、バンドルに含まれません。
リソース
ワイルドツール(GCC、ロールアップなど)で利用可能な課題は、角度コンポーネントのHTMLのようなテンプレートであり、それらの機能を分析できません。これにより、テンプレート内でどのディレクティブが参照されているかがわからないため、ツリーシェーキングサポートが効率が低下します。 AOTコンパイラは、ES2015モジュールのインポートを使用して、Angular HTMLのようなテンプレートをJavaScriptまたはTypeScriptに伝達します。このようにして、バンドリング中に効率的にツリーシェイクを行い、Angular、Shirdパーティライブラリ、または自分で定義されたすべての未使用のディレクティブを削除することができます。
リソース
帯域幅の使用削減のための応答のペイロード標準慣行の圧縮。ヘッダーAccept-Encodingの値を指定することにより、ブラウザは、クライアントのマシンで使用できる圧縮アルゴリズムをサーバーに示唆します。一方、サーバーは、応答を圧縮するためにどのアルゴリズムが選択されたかをブラウザに伝えるために、応答のContent-Encodingヘッダーの値を設定します。
ツーリング
ここでのツールは角度特異的ではなく、使用しているWeb/アプリケーションサーバーに完全に依存します。典型的な圧縮アルゴリズムは次のとおりです。
リソース
リソースの事前フェッチングは、ユーザーエクスペリエンスを向上させる素晴らしい方法です。資産を事前にフェッチすることができます(画像、スタイル、怠lazにロードされることを目的としたモジュールなど)またはデータができます。さまざまな事前フェッチング戦略がありますが、それらのほとんどはアプリケーションの詳細に依存しています。
ターゲットアプリケーションに何百もの依存関係を持つ巨大なコードベースがある場合、上記のプラクティスはバンドルを合理的なサイズに削減するのに役立たない場合があります(合理的なものは100kまたは2mになる可能性があります。
そのような場合、適切な解決策は、アプリケーションのモジュールの一部を怠zyにロードすることです。たとえば、eコマースシステムを構築しているとしましょう。この場合、ユーザー向けのUIとは独立して管理パネルをロードすることをお勧めします。管理者が新しい製品を追加する必要があると、そのために必要なUIを提供する必要があります。これは、ユースケース/ビジネス要件に応じて、「製品ページの追加」または管理パネル全体のみにすることができます。
ツーリング
次のルーティング構成があるとしましょう。
// Bad practice
const routes : Routes = [
{ path : '' , redirectTo : '/dashboard' , pathMatch : 'full' } ,
{ path : 'dashboard' , loadChildren : ( ) => import ( './dashboard.module' ) . then ( mod => mod . DashboardModule ) } ,
{ path : 'heroes' , loadChildren : ( ) => import ( './heroes.module' ) . then ( mod => mod . HeroesModule ) }
] ;ユーザーがURL:https://example.com/を使用してアプリケーションを初めて開くと、パスdashboardボードでレイジールートをトリガーする/dashboardにリダイレクトされます。 Angularがモジュールのブートストラップコンポーネントをレンダリングするために、ファイルdashboard.moduleとそのすべての依存関係をダウンロードする必要があります。その後、ファイルはJavaScript VMによって解析され、評価する必要があります。
追加のHTTP要求をトリガーし、最初のページのロード中に不必要な計算を実行することは、初期ページレンダリングを遅くするため、悪い練習です。デフォルトのページルートを怠lazyとして宣言することを検討してください。
キャッシュは、最近1つのリソースが要求された場合、近い将来に再びリクエストされる可能性があるというヒューリスティックを利用することにより、アプリケーションをスピードアップすることを目的としたもう1つの一般的な慣行です。
キャッシュデータには、通常、カスタムキャッシュメカニズムを使用します。静的資産をキャッシュするには、標準のブラウザキャッシングを使用するか、CACHESTORAGE APIを使用してサービスワーカーを使用できます。
アプリケーションの知覚されたパフォーマンスをより速くするには、アプリケーションシェルを使用します。
アプリケーションシェルは、アプリケーションがまもなく配信されることを示すために、ユーザーに表示する最小ユーザーインターフェイスです。アプリケーションシェルを動的に生成するには、使用済みのレンダリングプラットフォームに応じて条件付き要素を表示するカスタムディレクティブを備えたAngular Universalを使用できます(つまり、 platform-serverを使用するときにアプリシェルを除くすべてを非表示にします)。
ツーリング
リソース
サービスワーカーは、ブラウザにあるHTTPプロキシと考えることができます。クライアントから送信されたすべてのリクエストは、最初にサービスワーカーによって傍受され、それらを処理するか、ネットワークを通過させることができます。
ng add @angular/pwa実行することで、角度プロジェクトにサービスワーカーを追加できます
ツーリング
リソース
このセクションには、毎秒60フレーム(FPS)でよりスムーズなユーザーエクスペリエンスを提供するために適用できるプラクティスが含まれています。
enableProdModeを使用します開発モードでは、Angularは、変化の検出を実行しても、バインディングの追加の変更が得られないことを確認するためにいくつかの追加チェックを実行します。このようにして、フレームワークは、単方向のデータフローが守られていることを保証します。
生産のためにこれらの変更を無効にするためには、 enableProdModeを呼び出すことを忘れないでください。
import { enableProdMode } from '@angular/core' ;
if ( ENV === 'production' ) {
enableProdMode ( ) ;
}AOTは、ツリーシェーキングを実行することでより効率的なバンドルを達成するだけでなく、アプリケーションのランタイムパフォーマンスを改善するためにも役立ちます。 AOTの代替案は、実行時間を実行するJust-in-Timeコンピレーション(JIT)です。したがって、ビルドプロセスの一部としてコンパイルを実行することにより、アプリケーションのレンダリングに必要な計算量を減らすことができます。
ツーリング
ng serve --prodを使用したAngular-Cliリソース
典型的なシングルページアプリケーション(SPA)の通常の問題は、通常、コードが単一のスレッドで実行されることです。これは、60fpsでスムーズなユーザーエクスペリエンスを実現したい場合、個々のフレーム間で実行するために最大16msがレンダリングされていることを意味します。
巨大なコンポーネントツリーを備えた複雑なアプリケーションでは、変更検出が数百万のチェックを実行する必要がある場合、フレームのドロップを開始するのは難しくありません。 Angularの不可知論とDOMアーキテクチャから切り離されているおかげで、Webワーカーでアプリケーション全体(変更検出を含む)を実行し、メインUIスレッドをレンダリングのみに責任を負わせることができます。
ツーリング
リソース
従来のスパの大きな問題は、最初のレンダリングに必要なJavaScript全体が利用可能になるまでレンダリングできないことです。これは2つの大きな問題につながります。
サーバー側のレンダリングは、サーバー上の要求されたページを事前にレンダリングし、初期ページのロード中にレンダリングされたページのマークアップを提供することにより、この問題を解決します。
ツーリング
リソース
それぞれの非同期イベントでは、Angularはコンポーネントツリー全体で変化検出を実行します。変更を検出するコードはインラインキャッシング用に最適化されていますが、これは依然として複雑なアプリケーションでの重い計算になる可能性があります。変更検出のパフォーマンスを改善する方法は、最近のアクションに基づいて変更されることになっていないサブツリーに対してそれを実行しないことです。
ChangeDetectionStrategy.OnPush OnPush Change検出戦略により、コンポーネントツリーのサブツリーの変更検出メカニズムを無効にすることができます。 Change Detection Strategyを任意のコンポーネントにChangeDetectionStrategy.OnPushに設定することにより、コンポーネントが異なる入力を受信した場合にのみ、変更検出が実行されます。 Angularは、参照ごとに以前の入力と比較する場合、入力を異なると見なし、参照チェックの結果はfalseです。不変のデータ構造と組み合わせて、 OnPushそのような「純粋な」コンポーネントに大きなパフォーマンスの影響をもたらすことができます。
リソース
カスタム変更検出メカニズムを実装するもう1つの方法は、コンポーネントの変化検出器(CD)をdetachてreattachです。 CDをdetachと、Angularはコンポーネントサブツリー全体のチェックを実行しません。
通常、このプラクティスは、ユーザーアクションまたは外部サービスとの対話が必要以上に頻繁に変更検出をトリガーする場合に使用されます。そのような場合、変更検出器の剥離を検討し、変化検出を実行する場合にのみ再触媒することを検討する必要があります。
Angularの変化検出メカニズムは、ゾーンのおかげでトリガーされています。 Zone.jsモンキーパッチは、ブラウザ内のすべての非同期APIをパッチし、非同期コールバックの実行の終了時に変更検出をトリガーします。まれに、特定のコードを角度ゾーンのコンテキストの外側で実行し、したがって、変化検出メカニズムを実行せずに実行することを望む場合があります。そのような場合、 NgZoneインスタンスのrunOutsideAngularメソッドを使用できます。
例
下のスニペットでは、この練習を使用するコンポーネントの例を見ることができます。 _incrementPointsメソッドが呼び出されると、コンポーネントは10ミリ秒ごとに_pointsプロパティの増加を開始します(デフォルトで)。増加はアニメーションの幻想になります。この場合、コンポーネントツリー全体の変更検出メカニズムをトリガーしたくないため、10msごとに、Angular's Zoneのコンテキストの外で_incrementPointsを実行し、DOMを手動で更新できます( pointsセッターを参照)。
@ Component ( {
template : '<span #label></span>'
} )
class PointAnimationComponent {
@ Input ( ) duration = 1000 ;
@ Input ( ) stepDuration = 10 ;
@ ViewChild ( 'label' ) label : ElementRef ;
@ Input ( ) set points ( val : number ) {
this . _points = val ;
if ( this . label ) {
this . label . nativeElement . innerText = this . _pipe . transform ( this . points , '1.0-0' ) ;
}
}
get points ( ) {
return this . _points ;
}
private _incrementInterval : any ;
private _points : number = 0 ;
constructor ( private _ngZone : NgZone , private _pipe : DecimalPipe ) { }
ngOnChanges ( changes : any ) {
const change = changes . points ;
if ( ! change ) {
return ;
}
if ( typeof change . previousValue !== 'number' ) {
this . points = change . currentValue ;
} else {
this . points = change . previousValue ;
this . _ngZone . runOutsideAngular ( ( ) => {
this . _incrementPoints ( change . currentValue ) ;
} ) ;
}
}
private _incrementPoints ( newVal : number ) {
const diff = newVal - this . points ;
const step = this . stepDuration * ( diff / this . duration ) ;
const initialPoints = this . points ;
this . _incrementInterval = setInterval ( ( ) => {
let nextPoints = Math . ceil ( initialPoints + diff ) ;
if ( this . points >= nextPoints ) {
this . points = initialPoints + diff ;
clearInterval ( this . _incrementInterval ) ;
} else {
this . points += step ;
}
} , this . stepDuration ) ;
}
}警告:適切に使用していなければ、DOMの一貫性のない状態につながる可能性があるため、自分が何をしているのかを確信している場合にのみ、この練習を非常に慎重に使用します。また、上記のコードはWebworkersでは実行されないことに注意してください。 Webworker互換を可能にするには、Angularのレンダラーを使用してラベルの値を設定する必要があります。
Angularはゾーンを使用して、アプリケーションで発生したイベントを傍受し、変更検出を自動的に実行します。デフォルトでは、これはブラウザのマイクロタスクキューが空である場合に発生し、場合によっては冗長サイクルを呼び出すことがあります。 V9から、Angularは、 ngZoneEventCoalescingオンにすることにより、イベントの変更検出を合体する方法を提供します。
platformBrowser ( )
. bootstrapModule ( AppModule , { ngZoneEventCoalescing : true } ) ;上記の構成では、MicroTaskキューに接続する代わりに、 requestAnimationFrameによる検出の変更をスケジュールします。これにより、チェックが頻繁に実行され、計算サイクルが少なくなります。
警告: ngzoneeventcoalescing:trueは、一貫して実行されている変更検出で中継する既存のアプリを破る可能性があります。
リソース
引数として、 @Pipeデコレーターは、次の形式でリテラルオブジェクトを受け入れます。
interface PipeMetadata {
name : string ;
pure : boolean ;
}純粋なフラグは、パイプがグローバルな状態に依存せず、副作用を生成しないことを示しています。これは、同じ入力で呼び出されたときにパイプが同じ出力を返すことを意味します。このようにして、Angularは、パイプが呼び出されたすべての入力パラメーターの出力をキャッシュし、各評価でそれらを再計算する必要がないためにそれらを再利用できます。
pureプロパティのデフォルト値はtrueです。
*ngForディレクティブ*ngForディレクティブは、コレクションのレンダリングに使用されます。
trackByオプションを使用しますデフォルトで*ngFor参照によってオブジェクトの一意性を識別します。
つまり、開発者がアイテムのコンテンツの更新中にオブジェクトへの参照を破ると、Angular Angularは古いオブジェクトの削除と新しいオブジェクトの追加として扱います。リスト内の古いDOMノードを破壊し、その場所に新しいDOMノードを追加することに影響します。
開発者は、Angularのヒントを提供します。オブジェクトの一意性を識別する方法: *ngForディレクティブのtrackByオプションとしてカスタムトラッキング機能。追跡関数は、 indexとitem 2つの引数を取ります。 Angularは、追跡関数から返された値を使用して、アイテムのIDを追跡します。特定のレコードのIDを一意のキーとして使用することは非常に一般的です。
例
@ Component ( {
selector : 'yt-feed' ,
template : `
<h1>Your video feed</h1>
<yt-player *ngFor="let video of feed; trackBy: trackById" [video]="video"></yt-player>
`
} )
export class YtFeedComponent {
feed = [
{
id : 3849 , // note "id" field, we refer to it in "trackById" function
title : "Angular in 60 minutes" ,
url : "http://youtube.com/ng2-in-60-min" ,
likes : "29345"
} ,
// ...
] ;
trackById ( index , item ) {
return item . id ;
}
} UIに要素を追加する場合、通常、DOM要素をレンダリングすることが最も高価な操作です。主な作業は通常、要素をDOMに挿入し、スタイルを適用することによって引き起こされます。 *ngFor多くの要素をレンダリングすると、ブラウザ(特に古いもの)が遅くなり、すべての要素のレンダリングを終了するためにより多くの時間が必要になる場合があります。これはAngularに固有のものではありません。
レンダリング時間を短縮するには、次のことを試してください。
*ngForセクションでレンダリングされたDOM要素の量を減らす。通常、不要な/未使用のDOM要素は、テンプレートを何度も拡張することで生じます。その構造を再考すると、おそらく物事がはるかに簡単になります。ng-containerを使用してくださいリソース
*ngForの公式ドキュメントAngularは、変化検出サイクルごとにテンプレート式を実行します。 Change Detection Cyclesは、Promise Resolutions、HTTP結果、タイマーイベント、キープレス、マウスの動きなど、多くの非同期アクティビティによって引き起こされます。
式は迅速に終了するか、特に遅いデバイスでユーザーエクスペリエンスがドラッグされる場合があります。計算が高価な場合は、キャッシュ値を検討してください。
リソース
プラクティスのリストは、新しい/更新されたプラクティスを使用して、時間とともに動的に進化します。不足していることに気付いた場合、またはプラクティスを改善できると思われる場合は、問題やPRを発射することをためらわないでください。詳細については、以下の「貢献」セクションをご覧ください。
不足している、不完全または間違ったものに気付いた場合、プルリクエストは大歓迎です。ドキュメントに含まれていないプラクティスの議論については、問題を開いてください。
mit