Stories

Detail Return Return

筆記 | Angular 實現 keep-alive (路由複用) - Stories Detail

Angular 的路由複用策略(RouteReuseStrategy)是一種用於優化路由跳轉性能和提高用户體驗的機制。通過實現RouteReuseStrategy接口,後可以自定義路由的複用行為,避免不必要的組件銷燬和重建,同時保持組件的狀態。

以下是對Angular路由複用策略的詳細介紹:

一、基本概念

RouteReuseStrategy是 Angular 路由模塊提供的一個接口,用於控制路由的複用邏輯。當路由切換時,如果目標路由與當前路由使用相同的組件,通過實現這個接口可以避免組件的重新創建,而是複用現有的組件實例。

二、主要作用

  1. 避免不必要的組件銷燬和重建:

通過複用組件實例,可以減少 DOM 操作和組件生命週期鈎子的調用次數,從而提高應用的性能。

  1. 保持組件狀態:

在路由切換時,如果組件被複用,那麼它的狀態(如表單輸入、滾動位置等)也會被保留,從而提升用户體驗。

三、解決存在的問題(使用場景)

在項目中,我們採用了 Cesium 庫來構建並加載三維場景。開發過程中注意到一個用户體驗上的顯著痛點:每當頁面發生跳轉並重新返回至 Cesium 場景時,都需要重新進行整個三維場景的加載,這一過程不僅延長了用户的等待時間,降低了整體應用的流暢性,還額外增加了系統資源的消耗,對設備的性能提出了更高要求。

為了解決這個問題,計劃採取一種優化策略:將 Cesium 實例化的頁面進行 keep-alive 處理。通過這一技術手段,我們可以確保在頁面跳轉時,Cesium 場景及其加載的所有資源(如地形數據、模型等)能夠保持活躍狀態,而非被銷燬並重新加載。這樣,當用户再次訪問該頁面時,能夠立即看到之前已經加載完成的場景,無需經歷冗長的加載過程,從而顯著提升應用的響應速度和用户體驗。

四、實現方法

實現核心類:

實現 RouteReuseStrategy

  • shouldDetach()是否允許複用路由
  • store()當路由離開時會觸發,存儲路由
  • shouldAttach() 是否允許還原路由
  • retrieve()獲取存儲路由
  • shouldReuseRoute()進入路由觸發,是否同一路由時複用路由

流程:

  1. 使用 shouldDetach()把路由 /home 設置為允許複用;
  2. 然後通過 store()將路由快照儲存起來;
  3. 路由切換時會觸發 shouldReuseRoute()如果返回為真,(即:再次遇到 /home 路由後表示需要複用路由);
  4. 使用 shouldAttach() 檢查是否允許還原;
  5. 最後通過 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 }
}

Add a new Comments

Some HTML is okay.