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 }
}