• 作者:老汪软件技巧
  • 发表时间:2024-08-26 10:03
  • 浏览量:

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作为存储
    },
  ),
);

持久化选项

以下是一些常用的持久化选项:

版本控制与迁移

如果你的状态结构发生变化,比如字段重命名或新增字段,你可以使用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中的使用(/Yidadaa/Cha…

工具方法,createPersistStore

里面所有的状态store都是通过该Util方法创建的。

export function createPersistStoreextends object, M>(
  state: T,
  methods: (
    set: SetStoreState>,
    get: () => T & MakeUpdater,
  ) => M,
  persistOptions: SecondParam<typeof persistMakeUpdater>>,
) {
  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
              >);
            },
            update(updater) {
              const state = deepClone(get());
              updater(state);
              set({
                ...state,
                lastUpdateTime: Date.now(),
              });
            },
          } as M & MakeUpdater;
        },
      ),
      persistOptions as any,
    ),
  );
}

这个函数,在创建store的时候,添加了一个记录更新时间的字段,并在数据更新的时候自动更新时间。同时也支持手动触发标记更新。

参数:

函数体:

最后,将持久化选项persistOptions传递给persist中间件。

combine 是 zustand 提供的一个工具函数,用于组合状态和行为方法。它将状态和行为方法组合成一个新的对象,这样你就可以使用 set 和 get 方法来更新和访问状态。

具体来说,combine 接受两个参数:

这个组合让你可以把状态和方法结合在一起,然后传递给 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 = {};
      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;
    },
  },
);

可以看到,这里主要是这几件事

在这个项目中,我们也遇到了一个问题,在支持图片之后,存储的聊天记录里,很轻易地就超过了5M,因为GPT本质上是不支持文件的,只支持base64,聊天记录里有base64,导致几个来回之后就超过了5M。然后写入失败导致应用无法正常工作。

所以我们做了一个调整,写入前,先判断一下大小,过大则淘汰掉最老的那个记录。这种处理,在状态管理中实现就很轻松了,也不用用太大的心理顾虑。

结论

Zustand的持久化功能为React应用的状态管理提供了强大的支持,使得状态可以跨页面甚至跨会话持久化。通过上述示例,你应该能够理解如何在你的应用中实现状态的持久化。记得根据你的具体需求选择合适的存储引擎和配置选项。