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