漸進式水合(incremental hydration)是一種進階的水合(hydration)方式,能讓應用程式的某些區塊保持未水合狀態,並在需要時漸進觸發這些區塊的水合。
為什麼要使用漸進式水合?
漸進式水合是在完整應用程式水合之上建構的效能改善。它能產生更小的初始套件,同時仍提供與完整應用程式水合相當的最終使用者體驗。更小的套件能改善初始載入時間,進而降低 First Input Delay (FID) 與 Cumulative Layout Shift (CLS)。
漸進式水合也讓你能將可延遲檢視(deferrable views,@defer)用在以前可能無法延遲的內容上。具體來說,你現在可以把可延遲檢視用在首屏(above the fold)的內容上。在漸進式水合出現之前,把 @defer 區塊放在首屏會導致佔位內容先渲染出來,然後再被 @defer 區塊的主要範本內容取代,進而造成版面位移。漸進式水合則表示 @defer 區塊的主要範本在水合時會直接渲染,不會產生版面位移。
如何在 Angular 中啟用漸進式水合?
對於已經使用伺服器端渲染(SSR)與水合的應用程式,你可以啟用漸進式水合。請先依照 Angular SSR 指南 啟用伺服器端渲染,並依照 Angular 水合指南 啟用水合。
當你使用 provideClientHydration() 時,漸進式水合預設就會啟用。
import {bootstrapApplication, provideClientHydration} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [provideClientHydration()],
});
NOTE: 漸進式水合會依賴並自動啟用事件重播。如果你的清單中已有 withEventReplay(),可以放心將它移除。
若要退出漸進式水合,請使用 withNoIncrementalHydration():
import {
bootstrapApplication,
provideClientHydration,
withNoIncrementalHydration,
} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [provideClientHydration(withNoIncrementalHydration())],
});
漸進式水合如何運作?
漸進式水合建立在完整應用程式水合、可延遲檢視與事件重播之上。透過漸進式水合,你可以為 @defer 區塊加入額外的觸發條件,來定義漸進式水合的邊界。在延遲區塊加入 hydrate 觸發條件,是告訴 Angular 它應該在伺服器端渲染期間載入該延遲區塊的相依性,並渲染主要範本,而不是 @placeholder。在用戶端渲染時,相依性仍會被延遲,且延遲區塊的內容會保持未水合狀態,直到它的 hydrate 觸發條件被觸發為止。該觸發條件會告訴延遲區塊去取得它的相依性並水合內容。使用者在水合之前觸發的任何瀏覽器事件(特別是與你元件中註冊的監聽器相符的事件),都會先被排入佇列,並在水合程序完成後重播。
以觸發條件控制內容的水合
你可以指定 hydrate 觸發條件(hydrate triggers),來控制 Angular 何時載入並水合延遲的內容。這些是可與一般 @defer 觸發條件併用的額外觸發條件。
每個 @defer 區塊可以有多個 hydrate 事件觸發條件,以分號(;)分隔。只要有任一觸發條件被觸發,Angular 就會觸發水合。
hydrate 觸發條件有三種類型:hydrate on、hydrate when 與 hydrate never。
hydrate on
hydrate on 指定 @defer 區塊觸發水合的條件。
可用的觸發條件如下:
| 觸發條件 | 說明 |
|---|---|
hydrate on idle |
在瀏覽器閒置時觸發。支援可選的逾時設定。 |
hydrate on viewport |
在指定內容進入可視區域時觸發 |
hydrate on interaction |
在使用者與指定元素互動時觸發 |
hydrate on hover |
在滑鼠懸停於指定區域時觸發 |
hydrate on immediate |
在非延遲內容完成渲染後立即觸發 |
hydrate on timer |
在特定時間長度後觸發 |
hydrate on idle
hydrate on idle 觸發條件會在瀏覽器達到閒置狀態時,根據 requestIdleCallback 載入可延遲檢視的相依性並水合內容。
你可以選擇性地指定一個以毫秒為單位的逾時,傳給 requestIdleCallback。如果瀏覽器沒有及時排程該回呼,工作最遲會在指定的逾時時間執行。
@defer (hydrate on idle) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
<!-- With a 500ms timeout -->
@defer (hydrate on idle(500)) {
<large-cmp />
}
hydrate on viewport
hydrate on viewport 觸發條件會在指定內容使用 Intersection Observer API 進入可視區域時,載入可延遲檢視的相依性,並水合應用程式對應的頁面。
@defer (hydrate on viewport) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
hydrate on interaction
hydrate on interaction 觸發條件會在使用者透過 click 或 keydown 事件與指定元素互動時,載入可延遲檢視的相依性並水合內容。
@defer (hydrate on interaction) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
hydrate on hover
hydrate on hover 觸發條件會在滑鼠透過 mouseover 與 focusin 事件懸停於觸發區域時,載入可延遲檢視的相依性並水合內容。
@defer (hydrate on hover) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
hydrate on immediate
hydrate on immediate 觸發條件會立即載入可延遲檢視的相依性並水合內容。這表示延遲區塊會在所有其他非延遲內容完成渲染後,立刻載入。
@defer (hydrate on immediate) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
hydrate on timer
hydrate on timer 觸發條件會在指定時間長度後,載入可延遲檢視的相依性並水合內容。
@defer (hydrate on timer(500ms)) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
時間長度參數必須以毫秒(ms)或秒(s)指定。
hydrate when
hydrate when 觸發條件接受自訂的條件運算式,並在條件變為 truthy 時,載入可延遲檢視的相依性並水合內容。
@defer (hydrate when condition) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
NOTE: hydrate when 條件只有在它們是最上層未水合的 @defer 區塊時才會觸發。觸發條件所提供的條件是在父元件中指定的,而父元件必須先存在,才能被觸發。如果父區塊尚未水合,Angular 還無法解析該運算式。
hydrate never
hydrate never 讓使用者能指定延遲區塊中的內容應無限期保持未水合狀態,實際上成為靜態內容。請注意,這只適用於初始渲染。在後續的用戶端渲染期間,帶有 hydrate never 的 @defer 區塊仍會取得相依性,因為水合只適用於伺服器端渲染內容的初始載入。在下面的範例中,後續的用戶端渲染會在進入可視區域時載入 @defer 區塊的相依性。
@defer (on viewport; hydrate never) {
<large-cmp />
} @placeholder {
<div>Large component placeholder</div>
}
NOTE: 使用 hydrate never 會防止指定 @defer 區塊的整個巢狀子樹被水合。在該區塊底下巢狀的內容,不會有任何其他 hydrate 觸發條件被觸發。
Hydrate 觸發條件與一般觸發條件併用
Hydrate 觸發條件是與 @defer 區塊的一般觸發條件併用的額外觸發條件。水合是一種初始載入最佳化,這表示 hydrate 觸發條件只適用於該次初始載入。任何後續的用戶端渲染仍會使用一般觸發條件。
@defer (on idle; hydrate on interaction) {
<example-cmp />
} @placeholder {
<div>Example Placeholder</div>
}
在此範例中,在初始載入時,適用的是 hydrate on interaction。水合會在與 <example-cmp /> 元件互動時觸發。在任何後續以用戶端渲染的頁面載入中(例如當使用者點選一個 routerLink,載入包含此元件的頁面時),適用的則是 on idle。
漸進式水合如何處理巢狀 @defer 區塊?
Angular 的元件與相依性系統是階層式的,這表示水合任何元件都需要它的所有父層也一併水合。因此,如果巢狀的一組未水合 @defer 區塊中的子 @defer 區塊觸發了水合,水合會從最上層未水合的 @defer 區塊開始,一路觸發到被觸發的子區塊,並依該順序執行。
@defer (hydrate on interaction) {
<parent-block-cmp />
@defer (hydrate on hover) {
<child-block-cmp />
} @placeholder {
<div>Child placeholder</div>
}
} @placeholder {
<div>Parent Placeholder</div>
}
在上例中,懸停於巢狀 @defer 區塊會觸發水合。帶有 <parent-block-cmp /> 的父 @defer 區塊會先水合,然後帶有 <child-block-cmp /> 的子 @defer 區塊才接著水合。
限制
漸進式水合的限制與完整應用程式水合相同,包括直接操作 DOM 的限制,以及要求有效的 HTML 結構。更多細節請參閱水合指南的限制章節。
我還需要指定 @placeholder 區塊嗎?
需要。@placeholder 區塊的內容不會用於漸進式水合,但後續用戶端渲染的情況仍需要 @placeholder。如果你的內容不在初始載入所屬的路由上,那麼任何導覽到包含你的 @defer 區塊內容的路由時,都會像一般 @defer 區塊一樣渲染。因此在那些用戶端渲染的情況中,會渲染 @placeholder。