Zustand的持久化中介軟體允許你將狀態儲存在各種儲存中,例如localStorage
、AsyncStorage
或IndexedDB
等。這使得應用的狀態可以跨頁面持久化。也就是說使用者重新整理頁面或者關閉瀏覽器後重新開啟,應用的狀態依然可以被保留。
使用方法
首先,你需要從zustand
庫中匯入create
和persist
函式,以及createJSONStorage
輔助函式。
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
然後,使用persist
函式包裝你的Zustand store,並提供必要的配置。
下面是一個簡單的示例,演示瞭如何建立一個持久化的Zustand store:
export const useBearStore = create(
persist(
(set, get) => ({
bears: 0,
addABear: () => set({ bears: get().bears + 1 }),
}),
{
name: 'bear-storage', // 儲存中的專案名稱,必須是唯一的
storage: createJSONStorage(() => sessionStorage), // 使用sessionStorage作為儲存
},
),
);
持久化選項
以下是一些常用的持久化選項:
name
:儲存中使用的唯一鍵名。storage
:自定義儲存引擎。partialize
:選擇儲存部分狀態欄位。onRehydrateStorage
:儲存恢復時呼叫的監聽函式。version
:版本控制,用於處理儲存的相容性。migrate
:處理版本遷移的函式。merge
:自定義持久化值與當前狀態的合併方式。skipHydration
:跳過初始化時的自動恢復。
版本控制與遷移
如果你的狀態結構發生變化,比如欄位重新命名或新增欄位,你可以使用version
和migrate
選項來處理:
export const useVersionedStore = create(
persist(
(set, get) => ({
newField: 0,
}),
{
name: 'versioned-storage',
version: 1,
migrate: (persistedState, version) => {
if (version === 0) {
persistedState.newField = persistedState.oldField;
delete persistedState.oldField;
}
return persistedState;
},
},
),
);
手動觸發恢復
在某些情況下,你可能需要手動觸發狀態的恢復,可以使用rehydrate
方法:
await useBoundStore.persist.rehydrate();
檢查是否已恢復
使用hasHydrated
方法可以檢查狀態是否已經恢復:
const hasHydrated = useBoundStore.persist.hasHydrated();
案例實踐
這裡分享一個開源專案ChatGPT-Next-Web中的使用(https://github.com/Yidadaa/ChatGPT-Next-Web),這也是一個寶藏專案,可以快速搭建一個自己的GPT。專案中zustand用於全域性的狀態管理,主要用於儲存GPT對話聊天資訊。
工具方法,createPersistStore
裡面所有的狀態store都是透過該Util方法建立的。
export function createPersistStore<T extends object, M>(
state: T,
methods: (
set: SetStoreState<T & MakeUpdater<T>>,
get: () => T & MakeUpdater<T>,
) => M,
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
) {
return create(
persist(
combine(
{
...state,
lastUpdateTime: 0,
},
(set, get) => {
return {
...methods(set, get as any),
markUpdate() {
set({ lastUpdateTime: Date.now() } as Partial<
T & M & MakeUpdater<T>
>);
},
update(updater) {
const state = deepClone(get());
updater(state);
set({
...state,
lastUpdateTime: Date.now(),
});
},
} as M & MakeUpdater<T>;
},
),
persistOptions as any,
),
);
}
這個函式,在建立store的時候,新增了一個記錄更新時間的欄位,並在資料更新的時候自動更新時間。同時也支援手動觸發標記更新。
引數:
- state: 初始狀態物件。
- methods: 一個函式,接收set和get方法,返回一些自定義的方法。這些方法會被合併到最終的store中。
- persistOptions: 持久化的選項,這些選項將被傳遞給persist中介軟體。
函式體:
- 使用zustand的create函式建立一個store。
- 在create函式內部,首先使用persist和combine中介軟體。
- combine中介軟體用於合併多個store或提供額外的方法。在這裡,它合併了初始狀態和由methods函式提供的方法。
- 在combine的回撥函式內部,除了透過methods函式提供的方法外,還額外新增了兩個方法:markUpdate和update。
- markUpdate方法:更新lastUpdateTime欄位為當前時間。
- update方法:深複製當前狀態,對複製後的狀態應用更新函式,然後設定新的狀態和lastUpdateTime。
- 最後,將持久化選項persistOptions傳遞給persist中介軟體。
combine 是 zustand 提供的一個工具函式,用於組合狀態和行為方法。它將狀態和行為方法組合成一個新的物件,這樣你就可以使用 set 和 get 方法來更新和訪問狀態。
具體來說,combine 接受兩個引數:
- 1.初始狀態(上面的程式碼中的 state 和 lastUpdateTime)
- 2.行為方法(一個函式,接收 set 和 get 引數,並返回一些方法,比如 markUpdate 和 update)
這個組合讓你可以把狀態和方法結合在一起,然後傳遞給 persist,最終建立出一個帶有持久化功能的 store。
使用方式
有了工具函式,就可以用來建立具體的store了。以下是其中使用者配置資料的store,其他的也差不多,主要是store業務方法的完善。
export const useAppConfig = createPersistStore(
{ ...DEFAULT_CONFIG },
(set, get) => ({
reset() {
set(() => ({ ...DEFAULT_CONFIG }));
},
mergeModels(newModels: LLMModel[]) {
if (!newModels || newModels.length === 0) {
return;
}
const oldModels = get().models;
const modelMap: Record<string, LLMModel> = {};
for (const model of oldModels) {
// model.available = false;
modelMap[`${model.name}@${model?.provider?.id}`] = model;
}
for (const model of newModels) {
// model.available = Boolean(newModels.available);
modelMap[`${model.name}@${model?.provider?.id}`] = model;
}
set(() => ({
models: Object.values(modelMap),
}));
},
allModels() {},
}),
{
name: StoreKey.Config,
version: 3.9,
migrate(persistedState, version) {
const state = persistedState as ChatConfig;
if (version < 3.4) {
state.modelConfig.sendMemory = true;
state.modelConfig.historyMessageCount = 4;
state.modelConfig.compressMessageLengthThreshold = 1000;
state.modelConfig.frequency_penalty = 0;
state.modelConfig.top_p = 1;
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
state.dontShowMaskSplashScreen = false;
state.hideBuiltinMasks = false;
}
if (version < 3.5) {
state.customModels = "claude,claude-100k";
}
return state as any;
},
},
);
可以看到,這裡主要是這幾件事
- 把初始狀態傳進去
- 增加一些store的處理方法:mergeModels、reset
- 定義了遷移策略
在這個專案中,我們也遇到了一個問題,在支援圖片之後,儲存的聊天記錄裡,很輕易地就超過了5M,因為GPT本質上是不支援檔案的,只支援base64,聊天記錄裡有base64,導致幾個來回之後就超過了5M。然後寫入失敗導致應用無法正常工作。
所以我們做了一個調整,寫入前,先判斷一下大小,過大則淘汰掉最老的那個記錄。這種處理,在狀態管理中實現就很輕鬆了,也不用用太大的心理顧慮。
結論
Zustand的持久化功能為React應用的狀態管理提供了強大的支援,使得狀態可以跨頁面甚至跨會話持久化。透過上述示例,你應該能夠理解如何在你的應用中實現狀態的持久化。記得根據你的具體需求選擇合適的儲存引擎和配置選項。
如果你有任何問題或想要了解更多關於Zustand的資訊,請訪問Zustand官方文件。
本文由mdnice多平臺釋出