提問: 消費者從商品明細頁回到商品列表的時候,希望可以保留消費者先前瀏覽的位置。
我們可以透過頁面緩存機制來實現這個需求。
簡單來說,頁面緩存的工作原理是,當用戶第一次訪問某個頁面時,系統會把這個頁面的內容保存到緩存中。這就像把食物放進冰箱一樣,方便以後取用。當同一位用戶或其他用戶再次訪問這個頁面時,系統不需要再去伺服器請求新的內容,而是直接從緩存中提取已經保存的網頁內容。這樣能大大提高加載速度,因為從緩存中取資料比從伺服器獲取要快得多。
那麼緩存該存在哪裡呢?
頁面緩存可以儲存在多個地方,並且每種儲存方式都有其特點和適用場景。分享一些常見的儲存位置:
內存(
Memory
)
頁面數據直接儲存在應用的內存中,通常使用狀態管理庫(如NgRx
、Redux
、Vuex
)來管理。
優點是速度非常快,因為數據就存在應用程式的內存中,且也容易對應頻繁的數據變更。
但缺點是當應用程式生命週期結束時,緩存也會隨之丟失,因此不適合用來儲存需要長期保留的資訊(例如:使用者設定、登入狀態等)。
LocalStorage
瀏覽器提供的一種持久化存儲方式,可以將資料透過key-value
的方式進行儲存,且資料在瀏覽器關閉後仍然存在。
優點是資料可以在跨生命週期的瀏覽中都能夠存取,而且是瀏覽器提供的API
,容易使用。
不過缺點也很明顯,除了有容量限制外,也不適合存放敏感資料,因為可以透過瀏覽器的開發者工具一目了然。
SessionStorage
也是瀏覽器提供的儲存方式,不過資料僅在當前瀏覽器會話中有效,瀏覽器關閉後數據就會消失。
優缺點與LocalStorage
相同,特性上更適合用來儲存只在當次瀏覽器生命週期中所需要的資料。
這次我們選擇使用 Angular
並搭配 ngRx
來實現瀏覽位置和資料的緩存,因為當使用者離開應用再次訪問時,就不需要重新快取了。
設計理念如下:
首先透過 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