用localStorage快取Redux的state
問題
概念
對於目前普遍的“單頁應用”,其中的好處是,前端可以從容的處理較複雜的資料模型,同時基於資料模型可以進行變換,實現更為良好的互動操作。
良好的互動操作背後,其實是基於一個對應到頁面元件狀態的模型,隨便稱其為UI模型。
資料模型對應的是後端資料庫中的業務資料,UI模型對應的是使用者在瀏覽器一系列操作後元件所呈現的狀態。
這兩個模型不是對等的!
比如下圖中這個管控臺(不存在所謂的子頁面,來進行單頁路由的切換,而是一個類似portal的各塊元件的切換):
我們構建的這個單頁應用,後端的資料庫和提供的介面,是儲存和管理資料模型的狀態。
但是使用者操作管控臺中,左側皮膚的開啟/關閉、列表選中的專案、編輯皮膚的開啟等,這些UI模型的狀態均不會被後端記錄。
現象
當使用者強制進行頁面重新整理,或者關閉頁面後又再次開啟時,單頁應用雖然能從後端拉取資料記錄,但是頁面元件的狀態已經無法恢復了。
目前,多數的單頁應用的處理,就是在頁面重新整理或重新開啟後,拋棄之前使用者操作後的狀態,進到一個初始狀態。(當然,如果涉及較多內容編輯的,會提示使用者先儲存等等)
但這樣,顯然是 對互動的一種妥協。
方案設計
技術場景
我們的單頁應用是基於Redux+React構建。
元件的 大部分狀態 (一些非受控元件內部維護的state,確實比較難去記錄了)都記錄在Redux的store維護的state中。
正是因為Redux這種基於全域性的狀態管理,才讓“UI模型”可以清晰浮現出來。
所以,只要在瀏覽器的本地儲存(localStorage)中,將state進行快取,就可以(基本)還原使用者最後的互動介面了。
何時取
先說何時取,因為這塊好說。
假設我們已經存下了state,localStorage中就會存在一個序列化後的state物件。
在介面中還原state,只需要在應用初始化的時候,Redux建立store的時候取一次就可以。
...
const loadState = () => {
try { // 也可以容錯一下不支援localStorage的情況下,用其他本地儲存
const serializedState = localStorage.getItem(`state`);
if (serializedState === null) {
return undefined;
} else {
return JSON.parse(serializedState);
}
} catch (err) {
// ... 錯誤處理
return undefined;
}
}
let store = createStore(todoApp, loadState())
...
何時存
儲存state的方式很簡單:
const saveState = (state) => {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem(`state`, serializedState);
} catch (err) {
// ...錯誤處理
}
};
至於何時觸發儲存,一種簡(愚)單(蠢)的方式是,在每次state發生更新的時候,都去持久化一下。這樣就能讓本地儲存的state時刻保持最新狀態。
基於Redux,這也很容易做到。在建立了store後,呼叫subscribe方法可以去監聽state的變化。
// createStore之後
store.subscribe(() => {
const state = store.getState();
saveState(state);
})
但是,顯然,從效能角度這很不合理(不過也許在某些場景下有這個必要)。所以機智的既望同學,提議只在onbeforeunload事件上就可以。
window.onbeforeunload = (e) => {
const state = store.getState();
saveState(state);
};
所以,只要使用者重新整理或者關閉頁面時,都會默默記下當前的state狀態。
何時清空
一存一取做到後,特性就已實現。版本上線,使用者使用,本地快取了state,當前的應用毫無問題。
坑
但是當再次釋出新版本程式碼後,問題就來了。
新程式碼維護的state和之前的結構不一樣,使用者用新的程式碼,讀取自己本地快取的舊的state,難免會出錯。
然而使用者此時無論怎麼操作,都不會清楚掉自己本地快取的state(不詳細說了,主要就是因為上面loadState和saveState的邏輯,導致。。。錯誤的state會一直被反覆儲存,即使在developer tools中手動清除localStorage也不會有效果)
解
解決就是,state需要有個版本管理,當和程式碼的版本不一致時,至少進行個清空操作。
目前專案中,採用的以下方案:
直接利用state,在其中增加一個節點,來記錄version。即增加對應的action、reducer,只是為了維護version的值。
...
// Actions
export function versionUpdate(version = 0.1) {
return {
type : VERSION_UPDATE,
payload : version
};
}
...
儲存state的邏輯改動較小,就是在每次儲存的時候,要把當前程式碼的version更新到state。
...
window.onbeforeunload = (e) => {
store.dispatch({
type: `VERSION_UPDATE`,
payload: __VERSION__ // 程式碼全域性變數,隨工程配置一起處理即可。每次涉及需要更新state的時候,必須更新此版本號。
})
const state = store.getState();
saveState(state);
}
...
讀取state的時候,則要比較程式碼的版本和state的版本,不匹配則進行相應處理(清空則是傳給createStore的初始state為undefined即可)
export const loadState = () => {
try {
const serializedState = localStorage.getItem(`state`);
if (serializedState === null) {
return undefined;
} else {
let state = JSON.parse(serializedState);
// 判斷本地儲存的state版本,如果落後於程式碼的版本,則清空state
if (state.version < __VERSION__) {
return undefined;
} else {
return state;
}
}
} catch (err) {
// ...錯誤處理
return undefined;
}
};
其他參考
相關文章
- React中Redux持久化State到localStorageReactRedux持久化
- Cookie && Session && localStorage && sessionstorage && HTTP快取CookieSessionHTTP快取
- 黑科技:LocalStorage 快取機制快取
- localStorage和SessionStorage,Application,Cache快取SessionAPP快取
- 利用localstorage實現本地訊息快取快取
- localstorage實現帶過期時間的快取功能快取
- 前端巧用localStorage做“快取”,減少HTTP請求次數前端快取HTTP
- HTML5本地快取localStorage和sessionStorage的操作方法收集HTML快取Session
- Redux的State不應該全部放在Store裡Redux
- flutter 獲取應用快取以及清除快取Flutter快取
- react-native 之 state 和 props 以及 redux 和 react-reduxReactRedux
- 可複用的快取元件快取元件
- Flutter 記錄 - Flutter State Management (Redux)使用介紹FlutterRedux
- 用Java寫一個分散式快取——快取管理Java分散式快取
- Redis的快取穿透、快取雪崩、快取擊穿的區別Redis快取穿透
- 為什麼redux要返回一個新的state引發的血案Redux
- 快取穿透、快取擊穿、快取雪崩、快取預熱快取穿透
- Web應用的快取設計模式Web快取設計模式
- ListView的複用和快取機制View快取
- 如何定期清理DNS快取?清理DNS快取有什麼用?DNS快取
- 系統快取全解析4:應用程式資料快取快取
- Redux進階系列2: 如何合理地設計StateRedux
- 從CPU快取看快取的套路快取
- 快取穿透、快取擊穿、快取雪崩快取穿透
- 快取穿透、快取雪崩、快取擊穿快取穿透
- 為什麼 redux 要返回一個新的 state 引發的血案(二)Redux
- WEB 應用快取解析以及使用 Redis 實現分散式快取Web快取Redis分散式
- 用Java寫一個分散式快取——快取淘汰演算法Java分散式快取演算法
- Redis快取擊穿、快取穿透、快取雪崩Redis快取穿透
- HTTP快取——協商快取(快取驗證)HTTP快取
- [Redis]快取穿透/快取擊穿/快取雪崩Redis快取穿透
- 對於前端快取的理解(快取機制和快取型別)前端快取型別
- 瀏覽器的快取機制—強快取與協商快取瀏覽器快取
- 如何使用Python獲取、寫入localStoragePython
- 快取穿透 快取雪崩快取穿透
- Web應用中快取的七種武器Web快取
- 快取問題(一) 快取穿透、快取雪崩、快取併發 核心概念快取穿透
- Win10系統如何清理應用商店快取_win10清理應用商店快取的方法Win10快取