React中如何正确初始化结合encrypt-storage的zustand持久化状态?
问题
我正在使用zustand的persist插件存储应用状态,希望用localStorage但要对缓存加密,采用encrypt-storage工具实现加密。需要调用后端API获取加密密钥来初始化encrypt-storage,但API调用过程中storage仍为undefined,导致报错:Uncaught TypeError: can't access property "setItem", storage is undefined。
附上我尝试的代码:
import { EncryptStorage } from "encrypt-storage"; import { create } from "zustand"; import { devtools, persist, } from "zustand/middleware"; import { createJSONStorage } from "zustand/middleware" const fake_api = (ms: number) => new Promise(resolve => { setTimeout(resolve, ms) }) export function KeyResolver(callback: () => void) { const fn = async () => { // 模拟API请求获取密钥 await fake_api(2000); console.log("encryption key retrieved") encryptStorage.EKEY = 'secret-key-value'; encryptStorage.storage = new EncryptStorage(encryptStorage.EKEY, { stateManagementUse: true, }); callback(); }; if (!encryptStorage.EKEY) { fn(); } } interface IEncryptStorage { storage: undefined | EncryptStorage, EKEY: null | string, } export const encryptStorage: IEncryptStorage = { storage: undefined, EKEY: null, } const useOptimiserStore = create<IOptimiserStore>()( devtools( persist( (set) => ({ ...initialOtimiserStoreState, _hasHydrated: false, setHyderated: (val) => set({ _hasHydrated: val }) }), { name: "optimiser-storage", // @ts-expect-error storage: createJSONStorage(() => encryptStorage.storage), onRehydrateStorage: () => { KeyResolver(() => { useOptimiserStore.getState().setHyderated(true) }); } } ), { name: "optimiser-storage", } ) ); // 组件中使用方式: const Component = () => { const hasHyderated = useOptimiserStore(state => state._hasHydrated); if (!hasHyderated) { return <>retreiving encryption keys </> } return <div> ... </div> }
解决方案
核心问题是zustand初始化时encryptStorage.storage还未完成初始化,persist插件会立即尝试访问storage的方法,导致undefined报错。需要调整密钥获取和store初始化的顺序,确保storage准备好后再让persist生效。
方案一:异步初始化store(推荐)
把store的创建逻辑包装成异步函数,等密钥获取并初始化EncryptStorage后再执行:
import { EncryptStorage } from "encrypt-storage"; import { create } from "zustand"; import { devtools, persist, } from "zustand/middleware"; import { createJSONStorage } from "zustand/middleware" const fake_api = (ms: number) => new Promise<string>(resolve => { setTimeout(() => resolve('secret-key-value'), ms) }) // 异步获取密钥并初始化EncryptStorage async function initEncryptStorage(): Promise<EncryptStorage> { const key = await fake_api(2000); console.log("encryption key retrieved"); return new EncryptStorage(key, { stateManagementUse: true, }); } // 定义store的核心逻辑 const createOptimiserStore = (storage: EncryptStorage) => create<IOptimiserStore>()( devtools( persist( (set) => ({ ...initialOtimiserStoreState, _hasHydrated: false, setHyderated: (val) => set({ _hasHydrated: val }) }), { name: "optimiser-storage", storage: createJSONStorage(() => storage), onRehydrateStorage: () => (state) => { state?.setHyderated(true); } } ), { name: "optimiser-storage", } ) ); // 声明store变量 let useOptimiserStore: ReturnType<typeof createOptimiserStore>; // 导出初始化方法 export async function initStore() { const storage = await initEncryptStorage(); useOptimiserStore = createOptimiserStore(storage); } // 供组件使用的钩子 export function useOptimiserStoreHook() { if (!useOptimiserStore) { throw new Error("Store未初始化,请先调用initStore()"); } return useOptimiserStore; }
应用入口初始化
在应用入口文件(如main.tsx)中先执行初始化,再渲染组件:
import { initStore } from './path-to-your-store'; async function bootstrap() { await initStore(); const root = ReactDOM.createRoot(document.getElementById('root')!); root.render(<App />); } bootstrap();
组件中使用
const Component = () => { const useStore = useOptimiserStoreHook(); const hasHyderated = useStore(state => state._hasHydrated); if (!hasHyderated) { return <>正在获取加密密钥...</> } return <div> ... </div> }
方案二:自定义延迟适配层(同步store)
如果希望保持store同步创建,可以自定义一个storage适配层,在storage未准备好时轮询等待:
// 自定义适配storage,等待encryptStorage初始化完成 const delayedStorage: Pick<Storage, "getItem" | "setItem" | "removeItem"> = { getItem: async (key) => { while (!encryptStorage.storage) { await new Promise(resolve => setTimeout(resolve, 100)); } return encryptStorage.storage.getItem(key); }, setItem: async (key, value) => { while (!encryptStorage.storage) { await new Promise(resolve => setTimeout(resolve, 100)); } encryptStorage.storage.setItem(key, value); }, removeItem: async (key) => { while (!encryptStorage.storage) { await new Promise(resolve => setTimeout(resolve, 100)); } encryptStorage.storage.removeItem(key); } }; // 修改store的persist配置 const useOptimiserStore = create<IOptimiserStore>()( devtools( persist( (set) => ({ ...initialOtimiserStoreState, _hasHydrated: false, setHyderated: (val) => set({ _hasHydrated: val }) }), { name: "optimiser-storage", storage: createJSONStorage(() => delayedStorage), onRehydrateStorage: () => { KeyResolver(() => { useOptimiserStore.getState().setHyderated(true); }); } } ), { name: "optimiser-storage", } ) );
这种方式不需要修改应用入口的渲染逻辑,通过轮询等待storage初始化完成,避免直接访问undefined的storage。
内容的提问来源于stack exchange,提问作者Voneone
相关产品推荐
相关产品推荐

