Angular 的路由復用策略(RouteReuseStrategy)是一種用於優化路由跳轉性能和提高用戶體驗的機制。通過實現RouteReuseStrategy介面,後可以自定義路由的復用行為,避免不必要的組件銷毀和重建,同時保持組件的狀態。 以下是對Angular路由復用策略的詳細介紹: 一、基 ...
Angular 的路由復用策略(RouteReuseStrategy)是一種用於優化路由跳轉性能和提高用戶體驗的機制。通過實現RouteReuseStrategy
介面,後可以自定義路由的復用行為,避免不必要的組件銷毀和重建,同時保持組件的狀態。
以下是對Angular路由復用策略的詳細介紹:
一、基本概念
RouteReuseStrategy
是 Angular 路由模塊提供的一個介面,用於控制路由的復用邏輯。當路由切換時,如果目標路由與當前路由使用相同的組件,通過實現這個介面可以避免組件的重新創建,而是復用現有的組件實例。
二、主要作用
- 避免不必要的組件銷毀和重建:
通過復用組件實例,可以減少 DOM 操作和組件生命周期鉤子的調用次數,從而提高應用的性能。
- 保持組件狀態:
在路由切換時,如果組件被覆用,那麼它的狀態(如表單輸入、滾動位置等)也會被保留,從而提升用戶體驗。
三、解決存在的問題(使用場景)
在當前的安慶項目中,我們採用了 Cesium 庫來構建並載入三維場景。開發中,我們註意到一個用戶體驗上的顯著痛點:每當頁面發生跳轉並重新返回至 Cesium 場景時,都需要重新進行整個三維場景的載入,這一過程不僅延長了用戶的等待時間,降低了整體應用的流暢性,還額外增加了系統資源的消耗,對設備的性能提出了更高要求。
為瞭解決這個問題,計劃採取一種優化策略:將 Cesium 實例化的頁面進行 keep-alive 處理。通過這一技術手段,我們可以確保在頁面跳轉時,Cesium 場景及其載入的所有資源(如地形數據、模型等)能夠保持活躍狀態,而非被銷毀並重新載入。這樣,當用戶再次訪問該頁面時,能夠立即看到之前已經載入完成的場景,無需經歷冗長的載入過程,從而顯著提升應用的響應速度和用戶體驗。
四、實現方法
實現核心類:
實現 RouteReuseStrategy
shouldDetach()
是否允許復用路由store()
當路由離開時會觸發,存儲路由shouldAttach()
是否允許還原路由retrieve()
獲取存儲路由shouldReuseRoute()
進入路由觸發,是否同一路由時復用路由
流程:
- 使用
shouldDetach()
把路由 /home 設置為允許復用; - 然後通過
store()
將路由快照儲存起來; - 路由切換時會觸發
shouldReuseRoute()
如果返回為真,(即:再次遇到 /home 路由後表示需要復用路由); - 使用
shouldAttach()
檢查是否允許還原; - 最後通過
retrieve()
拿到路由快照並構建組件。
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
type MyDetachedRouteHandle = DetachedRouteHandle | null;
@Injectable()
export class MyReuseStrategy implements RouteReuseStrategy {
private static routeCache = new Map<string, DetachedRouteHandle>();
private static waitDelete: string | null; // 待刪除的快照
/**
* 用於刪除路由快照
*
* @param {string} url - 需要刪除路由的 URL。
* @return {void}
*/
public static deleteRouteSnapshot(url: string): void {
if (url[0] === '/') {
url = url.substring(1);
}
url = url.replace(/\//g, '_');
if (MyReuseStrategy.routeCache.has(url)) {
MyReuseStrategy.routeCache.delete(url);
}
MyReuseStrategy.waitDelete = url;
}
/**
* 用於清空路由快照
*/
public static clearRouteSnapshot(): void {
MyReuseStrategy.routeCache.clear();
MyReuseStrategy.waitDelete = null;
}
/**
* 進入路由時觸發,根據將來和當前路由配置和參數的比較確定當前路由是否應該重用。
*
* @param {ActivatedRouteSnapshot} future - 將來的路由快照。
* @param {ActivatedRouteSnapshot} curr - 當前路由快照。
* @return {boolean} 如果應該重用路由,則返回 true,否則返回 false。
*/
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return (
future.routeConfig === curr.routeConfig &&
JSON.stringify(future.params) === JSON.stringify(curr.params)
);
}
/**
* 基於路由數據確定路由是否應該分離。
* 表示對所有路由允許復用 如果有路由不想利用可以在這加一些業務邏輯判斷,這裡判斷路由是否有 keepAlive 數據判斷是否復用。
*
* @param {ActivatedRouteSnapshot} route - 要檢查分離的路由快照。
* @return {boolean} 如果應該分離路由則返回true,否則返回 false。
*/
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
return route.data.keepAlive;
}
/**
* 當路由離開時會觸發, 按 path 作為 key 存儲路由快照組件當前實例對象
* 如果路由配置為‘’,則出現Cannot reattach ActivatedRouteSnapshot created from a different route問題
*
* @param {ActivatedRouteSnapshot} route - 用於獲取完整路由 URL 的路由快照。
* @param {DetachedRouteHandle} handle - 要存儲的路由處理程邏輯。
* @return {void}
*/
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
const url = this.getFullRouteUrl(route);
if (MyReuseStrategy.waitDelete && MyReuseStrategy.waitDelete === url) {
// 如果待刪除是當前路由,且未存儲過則不存儲快照
MyReuseStrategy.waitDelete = null;
return;
}
MyReuseStrategy.routeCache.set(url, handle);
}
/**
* 根據 URL 確定是否應該附加路由。
* 若 URL 在緩存中有的都認為允許還原路由
*
* @param {ActivatedRouteSnapshot} route - 要檢查的路由快照。
* @return {boolean} 是否應該附加路由。
*/
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
const url = this.getFullRouteUrl(route);
return MyReuseStrategy.routeCache.has(url);
}
/**
* 從緩存中獲取快照,若無則返回 null
*
* @param {ActivatedRouteSnapshot} route - 要檢查的路由快照
* @return {MyDetachedRouteHandle} MyDetachedRouteHandle
*/
public retrieve(route: ActivatedRouteSnapshot): MyDetachedRouteHandle {
const url = this.getFullRouteUrl(route);
let handle = MyReuseStrategy.routeCache.has(url)
? MyReuseStrategy.routeCache.get(url)
: null;
handle = handle ? handle : null;
return handle;
}
/**
* 基於提供的 ActivatedRouteSnapshot 獲取完整的路由 URL。
*
* @param {ActivatedRouteSnapshot} route - 用於生成完整路由 URL 的 ActivatedRouteSnapshot
* @return {string} 過濾、連接和替換字元後的完整路由URL。
*/
private getFullRouteUrl(route: ActivatedRouteSnapshot): string {
return this.getFullRouteUrlPaths(route).filter(Boolean).join('/').replace(/\//g, '_');
}
/**
* 基於提供的 ActivatedRouteSnapshot 獲取完整的路由 URL 的 paths。
*
* @param {ActivatedRouteSnapshot} route - 用於生成完整路由 URL 的 ActivatedRouteSnapshot
* @return {string []}
*/
private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
const paths = route.url.map(urlSegment => urlSegment.path);
return route.parent ? [...this.getFullRouteUrlPaths(route.parent), ...paths] : paths;
}
}
將核心類註冊到模塊中:
在 Angular 應用中,需要將自定義的路由復用策略註入到應用的根模塊(通常是 AppModule )中。這可以通過在 providers 數組中添加一個提供器來實現;
...
providers: [
{ provide: RouteReuseStrategy, useClass: MyReuseStrategy }
];
...
路由中開心 keepAlive:
{
path: 'home',
...
data: { keepAlive: true }
}