iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0
Modern Web

轉生成前端工程師後,30步離開新手村!系列 第 18

# 前端實作案例分享:  透過Store進行頁面緩存

  • 分享至 

  • xImage
  •  

提問: 消費者從商品明細頁回到商品列表的時候,希望可以保留消費者先前瀏覽的位置。
https://ithelp.ithome.com.tw/upload/images/20241002/20169487EZlucbA73t.jpg

我們可以透過頁面緩存機制來實現這個需求。


什麼是頁面緩存?

簡單來說,頁面緩存的工作原理是,當用戶第一次訪問某個頁面時,系統會把這個頁面的內容保存到緩存中。這就像把食物放進冰箱一樣,方便以後取用。當同一位用戶或其他用戶再次訪問這個頁面時,系統不需要再去伺服器請求新的內容,而是直接從緩存中提取已經保存的網頁內容。這樣能大大提高加載速度,因為從緩存中取資料比從伺服器獲取要快得多。

那麼緩存該存在哪裡呢?

頁面緩存可以儲存在多個地方,並且每種儲存方式都有其特點和適用場景。分享一些常見的儲存位置:

  • 內存(Memory
    頁面數據直接儲存在應用的內存中,通常使用狀態管理庫(如 NgRxReduxVuex)來管理。
    優點是速度非常快,因為數據就存在應用程式的內存中,且也容易對應頻繁的數據變更。
    但缺點是當應用程式生命週期結束時,緩存也會隨之丟失,因此不適合用來儲存需要長期保留的資訊(例如:使用者設定、登入狀態等)。

  • LocalStorage
    瀏覽器提供的一種持久化存儲方式,可以將資料透過 key-value 的方式進行儲存,且資料在瀏覽器關閉後仍然存在。
    優點是資料可以在跨生命週期的瀏覽中都能夠存取,而且是瀏覽器提供的 API,容易使用。
    不過缺點也很明顯,除了有容量限制外,也不適合存放敏感資料,因為可以透過瀏覽器的開發者工具一目了然。

  • SessionStorage
    也是瀏覽器提供的儲存方式,不過資料僅在當前瀏覽器會話中有效,瀏覽器關閉後數據就會消失。
    優缺點與 LocalStorage 相同,特性上更適合用來儲存只在當次瀏覽器生命週期中所需要的資料。


實作方式:

這次我們選擇使用 Angular 並搭配 ngRx 來實現瀏覽位置和資料的緩存,因為當使用者離開應用再次訪問時,就不需要重新快取了。

設計理念如下:
https://ithelp.ithome.com.tw/upload/images/20241002/20169487Ye148q1Kzn.png
首先透過 interface 來規範緩存的資料和規格,再來在 reducer 中加入一個 state 負責管理緩存狀態,action 負責更新緩存。

需要緩存的頁面,可以在 OnInit life hook 的時候判斷是要使用緩存還是載入資料,並在 OnDestroy life hook 的時候將使用者最後瀏覽的狀態更新到 store 中。

最後若有外部呼叫需要手動清除緩存,也能透過呼叫 action 來完成。

我們來看看程式碼。

export interface IComponentCache {
  //#region 資料
  products: Array<Product>;
  //#endregion 資料

  //#region 使用者操作狀態類
  // 瀏覽頁數
  pageCnt: number;
  // 複數資料瀏覽位置
  multiItemScrolledRecord: Map<string, number>;
  // 單一資料瀏覽位置
  itemScrolledRecord: number;
  //#endregion 使用者操作狀態類
}

首先是 interface 的部分,主要將裡面的 property 分成資料和定位兩部分。值得一提的是,定位我們可以透過 Map 型別來儲存多筆資料,並使用 ID 進行管理。

/**
 * 緩存 store
 */
export const CacheActions = createActionGroup({
  source: 'CACHE',
  events: {
    UPDATE_CACHE: props<{
      cache: IComponentCache | null;
    }>(),
  },
});

/**
 * STATE 初始狀態
 */
export const initialState: ICacheState = {
  componentCache: null,
};

/**
 * STATE 宣告
 */
export interface ICacheState {
  componentCache: IComponentCache | null;
}

/**
 * REDUCER 宣告
 */
export const cacheReducer = createReducer(
  initialState,
  on(CacheActions.UPDATE_CACHE, (state, action) => {
    return {
      ...state,
      componentCache: action.cache,
    };
  }),
);

/**
 * selector 宣告
 */
export const selectComponentCache = createSelector(
  selectCache,
  (state: ICacheState) => state.componentCache
);

Store 的部分 componentCache 可以是 null 或是物件,透過這個方式來判斷是否載入緩存。

ngOnInit(): void {
  this.store
    .select(selectComponentCache)
    .pipe(take(1))
    .subscribe((cache) => {
      if (cache) {
        // 快取還原處理
        this.isCache = true;
      } else {
        // 初始化
        this.isCache = false;
      }
      // 無論緩存與否都要的流程
    });
}

ngAfterViewInit(): void {
  if (this.isCache) {
    // 還原滾動位置
  }
}

ngOnDestroy(): void {
  this.store.dispatch(
    CacheActions.UPDATE_CACHE({
      componentCache: {
        // 要快取的資料
      },
    })
  );
}

最後是緩存 component 的實作,值得一提的是,在 ngAfterViewInit 的時候我們才還原滾動位置,這樣可以確保使用 viewChild 或是 viewportScroller 的時候能抓取到正確的定位和 DOM,避免尚未渲染完畢就進行滾動。


以上就是本次緩存實作的分享!下一篇文章會分享如何實作全域的動態 NavBar。


圖片出處: https://medium.com/@mena.meseha/3-major-problems-and-solutions-in-the-cache-world-155ecae41d4f


上一篇
# 前端實作案例分享: 攔截瀏覽器回上一頁
下一篇
# 前端實作案例分享:  隨著頁面改變樣式的NavBar
系列文
轉生成前端工程師後,30步離開新手村!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言