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

iNSlog發表於2024-09-03

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

相關文章