遅延可能ビュー(別名@deferブロック)は、ページの初期レンダリングに厳密に必要ないコードの読み込みを遅らせることで、アプリケーションの初期バンドルサイズを削減します。これにより、多くの場合、初期読み込みが高速化され、特にLargest Contentful Paint(LCP)とTime to First Byte(TTFB)に関してCore Web Vitals(CWV)が向上します。
この機能を使用するには、テンプレートのセクションを@deferブロックで宣言的にラップします。
@defer { <large-component />}
@deferブロック内のコンポーネント、ディレクティブ、パイプのコードは、別のJavaScriptファイルに分割され、残りのテンプレートがレンダリングされた後、必要な場合にのみ読み込まれます。
遅延可能ビューは、さまざまなトリガーやプリフェッチオプションおよびプレースホルダー、読み込み、エラー状態の管理のためのサブブロックをサポートしています。
どの依存関係が遅延されますか?
アプリケーションを読み込む際に、コンポーネント、ディレクティブ、パイプ、およびコンポーネントCSSスタイルを遅延させることができます。
@deferブロック内の依存関係を遅延させるためには、2つの条件を満たす必要があります。
- スタンドアロンである必要があります。 スタンドアロンでない依存関係は遅延させることができず、
@deferブロック内にあっても、依然として先に読み込まれます。 - 同じファイル内の
@deferブロックの外側では参照できません。@deferブロックの外側で参照される場合、またはViewChildクエリ内で参照される場合、依存関係は先に読み込まれます。
@deferブロックで使用されるコンポーネント、ディレクティブ、パイプの_推移的_依存関係は、厳密にはスタンドアロンである必要はありません。推移的依存関係は依然としてNgModuleで宣言でき、遅延読み込みに参加できます。
Angularのコンパイラは、@deferブロックで使用される各コンポーネント、ディレクティブ、およびパイプに対して動的インポート文を生成します。ブロックのメインコンテンツは、すべてのインポートが解決された後にレンダリングされます。Angularは、これらのインポートの特定の順序を保証しません。
遅延読み込みのさまざまなステージを管理する方法
@deferブロックには、遅延読み込みプロセスにおけるさまざまなステージを適切に処理できるように、いくつかのサブブロックがあります。
@defer
これは、遅延読み込みされるコンテンツのセクションを定義するプライマリブロックです。これは最初はレンダリングされません。遅延コンテンツは、指定されたトリガーが発生するか、when条件が満たされたときに読み込まれてレンダリングされます。
デフォルトでは、@deferブロックはブラウザの状態がアイドルになるとトリガーされます。
@defer { <large-component />}
@placeholderでプレースホルダーコンテンツを表示する
デフォルトでは、@deferブロックはトリガーされる前にコンテンツをレンダリングしません。
@placeholderは、@deferブロックがトリガーされる前に表示するコンテンツを宣言するオプションのブロックです。
@defer { <large-component />} @placeholder { <p>プレースホルダーコンテンツ</p>}
オプションですが、特定のトリガーでは、@placeholderまたはテンプレート参照変数のいずれかの存在が必要になる場合があります。詳細については、トリガーセクションを参照してください。
Angularは、読み込みが完了すると、プレースホルダーコンテンツをメインコンテンツに置き換えます。プレースホルダーセクションには、プレーンHTML、コンポーネント、ディレクティブ、パイプなど、あらゆるコンテンツを使用できます。プレースホルダーブロックの依存関係は先に読み込まれます。
@placeholderブロックは、プレースホルダーコンテンツが最初にレンダリングされた後にこのプレースホルダーを表示するminimum時間を指定するオプションのパラメータを受け入れます。
@defer { <large-component />} @placeholder (minimum 500ms) { <p>プレースホルダーコンテンツ</p>}
このminimumパラメータは、ミリ秒(ms)または秒(s)の時間増分で指定されます。このパラメータを使用して、遅延された依存関係がすばやく取得された場合にプレースホルダーコンテンツが高速でちらつくのを防ぐことができます。
@loadingで読み込みコンテンツを表示する
@loadingブロックは、遅延された依存関係が読み込まれている間に表示するコンテンツを宣言できるようにするオプションのブロックです。これは、読み込みがトリガーされると@placeholderブロックを置き換えます。
@defer { <large-component />} @loading { <img alt="読み込み中..." src="loading.gif" />} @placeholder { <p>プレースホルダーコンテンツ</p>}
その依存関係は先に読み込まれます(@placeholderと同様)。
@loadingブロックは、遅延された依存関係がすばやく取得された場合に発生する可能性のあるコンテンツの高速なちらつきを防ぐために、2つのパラメータを受け入れます。
minimum- このプレースホルダーを表示する最小時間after- 読み込みが開始されてから読み込みテンプレートを表示するまでの待機時間
@defer { <large-component />} @loading (after 100ms; minimum 1s) { <img alt="読み込み中..." src="loading.gif" />}
両方のパラメータは、ミリ秒(ms)または秒(s)の時間増分で指定されます。さらに、両方のパラメータのタイマーは、読み込みがトリガーされた直後に開始されます。
遅延読み込みが失敗した場合に@errorでエラー状態を表示する
@errorブロックは、遅延読み込みが失敗した場合に表示するオプションのブロックです。@placeholderや@loadingと同様に、@errorブロックの依存関係は先に読み込まれます。
@defer { <large-component />} @error { <p>大型コンポーネントの読み込みに失敗しました。</p>}
トリガーを使用した遅延コンテンツ読み込みの制御
遅延コンテンツがいつ読み込まれて表示されるかを制御するトリガーを指定できます。
@deferブロックがトリガーされると、プレースホルダーコンテンツが遅延読み込みされたコンテンツに置き換えられます。
複数のイベントトリガーを、セミコロン(;)で区切って定義でき、OR条件として評価されます。
トリガーには、onとwhenの2種類があります。
on
onは、@deferブロックがトリガーされる条件を指定します。
使用可能なトリガーは次のとおりです。
| トリガー | 説明 |
|---|---|
idle |
ブラウザがアイドル状態になるとトリガーされます。 |
viewport |
指定されたコンテンツがビューポートに入るとトリガーされます。 |
interaction |
ユーザーが指定された要素と対話するとトリガーされます。 |
hover |
マウスが指定された領域にホバーするとトリガーされます。 |
immediate |
遅延されていないコンテンツのレンダリングが完了した直後にトリガーされます。 |
timer |
特定の期間後にトリガーされます。 |
idle
idleトリガーは、ブラウザがrequestIdleCallbackに基づいてアイドル状態に達すると、遅延コンテンツを読み込みます。これは、@deferブロックのデフォルトの動作です。
<!-- @defer (on idle) -->@defer { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
viewport
viewportトリガーは、Intersection Observer APIを使用して、指定されたコンテンツがビューポートに入ると、遅延コンテンツを読み込みます。観測されるコンテンツは、@placeholderコンテンツまたは明示的な要素参照にできます。
デフォルトでは、@deferは、プレースホルダーがビューポートに入っているかどうかを観察します。このように使用されるプレースホルダーは、単一のルート要素を持つ必要があります。
@defer (on viewport) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
または、@deferブロックと同じテンプレート内に、ビューポートに入っているかどうかが監視される要素としてテンプレート参照変数を指定できます。この変数は、ビューポートトリガーのパラメータとして渡されます。
<div #greeting>こんにちは!</div>@defer (on viewport(greeting)) { <greetings-cmp />}
interaction
interactionトリガーは、ユーザーがclickまたはkeydownイベントを通じて指定された要素と対話すると、遅延コンテンツを読み込みます。
デフォルトでは、プレースホルダーが対話要素として機能します。このように使用されるプレースホルダーは、単一のルート要素を持つ必要があります。
@defer (on interaction) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
Alternatively, you can specify a template reference variable in the same template as the @defer block as the element that is watched for interactions. This variable is passed in as a parameter on the viewport trigger.
<div #greeting>こんにちは!</div>@defer (on interaction(greeting)) { <greetings-cmp />}
hover
hoverトリガーは、マウスがmouseoverイベントとfocusinイベントを通じてトリガーされた領域にホバーすると、遅延コンテンツを読み込みます。
デフォルトでは、プレースホルダーが対話要素として機能します。このように使用されるプレースホルダーは、単一のルート要素を持つ必要があります。
@defer (on hover) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
または、@deferブロックと同じテンプレート内に、ビューポートに入っているかどうかが監視される要素としてテンプレート参照変数を指定できます。この変数は、ビューポートトリガーのパラメータとして渡されます。
<div #greeting>こんにちは!</div>@defer (on hover(greeting)) { <greetings-cmp />}
immediate
immediateトリガーは、遅延コンテンツをすぐに読み込みます。これは、遅延ブロックは、他のすべての遅延されていないコンテンツのレンダリングが完了するとすぐに読み込まれることを意味します。
@defer (on immediate) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
timer
timerトリガーは、指定された期間後に遅延コンテンツを読み込みます。
@defer (on timer(500ms)) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
期間パラメータは、ミリ秒(ms)または秒(s)で指定する必要があります。
when
whenトリガーは、カスタムの条件式を受け取り、条件が真になったときに遅延コンテンツを読み込みます。
@defer (when condition) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
これは1回限りの操作です。@deferブロックは、真になった後に偽の値に変更された場合、プレースホルダーに復帰しません。
prefetchでデータをプリフェッチする
遅延コンテンツが表示される条件を指定することに加えて、プリフェッチトリガーをオプションで指定できます。このトリガーを使用すると、@deferブロックに関連付けられたJavaScriptを、遅延コンテンツが表示される前に読み込むことができます。
プリフェッチを使用すると、ユーザーが実際に@deferブロックを表示または対話する前に、ユーザーがすぐに対話する可能性のあるリソースのプリフェッチを開始します。これにより、リソースをより高速に利用できるようにするなど、より高度な動作が可能になります。
プリフェッチトリガーは、ブロックのメイントリガーと同様に指定できますが、prefetchキーワードを前に付けます。ブロックのメイントリガーとプリフェッチトリガーは、セミコロン(;)で区切られます。
次の例では、プリフェッチはブラウザがアイドル状態になると開始され、ブロックのコンテンツは、ユーザーがプレースホルダーと対話したときにのみレンダリングされます。
@defer (on interaction; prefetch on idle) { <large-cmp />} @placeholder { <div>大型コンポーネントのプレースホルダー</div>}
@deferブロックのテスト
Angularは、@deferブロックのテストと、テスト中のさまざまな状態のトリガーを簡素化するTestBed APIを提供しています。デフォルトでは、テスト内の@deferブロックは、実際のアプリケーションで@deferブロックが動作するのと同じように動作します。状態を手動でステップ実行する場合は、TestBedの構成で@deferブロックの動作をManualに切り替えることができます。
it('さまざまな状態で`@defer`ブロックをレンダリングする', async () => { // 手動制御のために`@defer`ブロックの動作を"一時停止"状態から開始するように構成します。 TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual}); @Component({ // ... template: ` @defer { <large-component /> } @placeholder { プレースホルダー } @loading { 読み込み中... } ` }) class ComponentA {} // コンポーネントのfixtureを作成します。 const componentFixture = TestBed.createComponent(ComponentA); // すべての`@defer`ブロックのfixtureを取得し、最初のブロックを取得します。 const deferBlockFixture = (await componentFixture.getDeferBlocks())[0]; // デフォルトでプレースホルダー状態をレンダリングします。 expect(componentFixture.nativeElement.innerHTML).toContain('プレースホルダー'); // 読み込み状態をレンダリングし、レンダリングされた出力を検証します。 await deferBlockFixture.render(DeferBlockState.Loading); expect(componentFixture.nativeElement.innerHTML).toContain('読み込み中'); // 最終状態をレンダリングし、出力を検証します。 await deferBlockFixture.render(DeferBlockState.Complete); expect(componentFixture.nativeElement.innerHTML).toContain('large works!');});
@deferはNgModuleと連携しますか?
@deferブロックは、スタンドアロンコンポーネントとNgModuleベースのコンポーネント、ディレクティブ、パイプの両方と互換性があります。ただし、スタンドアロンコンポーネント、ディレクティブ、パイプのみを遅延させることができます。NgModuleベースの依存関係は遅延されず、即座に読み込まれたバンドルに含まれます。
@deferブロックとホットモジュールリロード(HMR)との互換性
ホットモジュール置換(HMR)がアクティブな場合、すべての@deferブロックチャンクが即座にフェッチされ、設定されたトリガーを上書きします。標準的なトリガー動作を復元するには、--no-hmrフラグを使用してアプリケーションを提供し、HMRを無効にする必要があります。
@deferは、サーバーサイドレンダリング(SSR)と静的サイト生成(SSG)とどのように連携しますか?
デフォルトでは、サーバー上でアプリケーションをレンダリングする際(SSRまたはSSGを使用する場合)、@deferブロックは常にその@placeholder(プレースホルダーが指定されていない場合は何も表示されません)をレンダリングし、トリガーは呼び出されません。クライアント側では、@placeholderの内容が水和され、トリガーがアクティブになります。
サーバー上(SSRとSSGの両方)で@deferブロックのメインコンテンツをレンダリングするには、Incremental Hydration機能を有効にして、必要なブロックのhydrateトリガーを構成できます。
ビューを遅延させるためのベストプラクティス
ネストされた@deferブロックによるカスケード読み込みを避ける
ネストされた@deferブロックがある場合は、同時に読み込まれないように、異なるトリガーを指定する必要があります。これにより、カスケードリクエストが発生し、ページ読み込みのパフォーマンスが低下する可能性があります。
レイアウトのシフトを避ける
初期読み込み時にユーザーのビューポートに表示されるコンポーネントを遅延させることは避けてください。これを行うと、累積レイアウトシフト(CLS)が増加するため、Core Web Vitalsに悪影響を与える可能性があります。
必要な場合、初期ページレンダリング中にコンテンツが読み込まれるimmediate、timer、viewport、カスタムwhenトリガーは避けてください。