Zustand:状态持久化在项目中的应用

news/2024/9/22 9:40:15/文章来源:https://www.cnblogs.com/miniwa/p/18378609

Zustand的持久化中间件允许你将状态存储在各种存储中,例如localStorageAsyncStorageIndexedDB等。这使得应用的状态可以跨页面持久化。也就是说用户刷新页面或者关闭浏览器后重新打开,应用的状态依然可以被保留。

使用方法

首先,你需要从zustand库中导入createpersist函数,以及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:跳过初始化时的自动恢复。

版本控制与迁移

如果你的状态结构发生变化,比如字段重命名或新增字段,你可以使用versionmigrate选项来处理:

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多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/786896.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

060、Vue3+TypeScript基础,插槽的基础用法

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vue// 引入emitter用于全局事件总线 // import emitter from @/utils/emitterconst app = createApp(App);// App.vue的根元素id为app app.mou…

059、Vue3+TypeScript基础,页面通讯之父组件provide数据,子孙组件用inject直接使用

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vue// 引入emitter用于全局事件总线 // import emitter from @/utils/emitterconst app = createApp(App);// App.vue的根元素id为app app.mou…

058、Vue3+TypeScript基础,页面通讯之父页面使用$parent的用法

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vue// 引入emitter用于全局事件总线 // import emitter from @/utils/emitterconst app = createApp(App);// App.vue的根元素id为app app.mou…

平面几何基本功:用导角法解决若干问题

引理1 如图, 设锐角\(\small \triangle ABC\)的外接圆为\(\small\Omega, X,Y,Z\)分别是劣弧\(\small\mathop{BC}\limits^\frown,\mathop{AC}\limits^\frown,\mathop{AB}\limits^\frown\)的中点.证明:\(\small\triangle XYZ\)的垂心是\(\small\triangle ABC\)的内心.分析:易知…

全网最适合入门的面向对象编程教程:38 Python常用复合数据类型-使用列表实现堆栈、队列和双端队列

在 Python 中,列表(list)是一种非常灵活的数据结构,可以用来实现堆栈(stack)、队列(queue)和双端队列(deque)。这些数据结构虽然在使用时遵循不同的操作规则,但都可以通过 Python 列表来高效地实现。全网最适合入门的面向对象编程教程:38 Python 常用复合数据类型-…

985计算机院倒数第一某废物的自述

985计算机院倒数第一某废物的自述 严格来说是某双一流,但无所谓了我我来自某发展一般的小县城,但好在家境还算**中等** 我高中成绩一直一般,但好在高三小镇做题家buff生效,在高考中超常发挥,上了一所想都没想过的某双一流 我选择专业时奉承母令,选择了计算机专业,也算是…

CLRCore

1.CLR:公共语言运行时,就是IL(中间语言)的运行环境;安装.net Framewrok的时会安装CLR 2.堆栈内存分配: CLR进行内存的分配 值类型分配在栈中,变量和值都是在线程栈中(结构体是输出值类型,结构体默认继承system.valuetype,所以不能继承其他类了,结构体不能有无参构造函…

CSP-S 2024 游记

壹 我有一个朋友叫小 W ,他最近有点闷。 我问他为什么闷,他跟我说他根本就没准备初赛。 我说你这么牛,连初赛都不用准备。 他说,他在梦中见到了 ddz ,他问 ddz 没准备初赛怎么办, ddz 给他的答复是:不是,哥们。你都免初赛了还问我干啥啊。 贰 我喜欢月光。 空空,不可控…

别样的ABC大战

前言:BYD ABC 大战。此事发生于2024年3月,为保护隐私(有的人应该能看出来哈哈),人物名字均使用字母代替。故事虽根据真实事件改编,但较为夸张。 一天,W老师给我发来微信。她说:“你敢不敢和其他人举行ABC大战?”我豪爽的答应了:“我当然敢!”周六下午在花园路XX号举…