页面重载时Jotai IndexedDB持久化原子重置为null,如何实现正确重水合?
页面重载时Jotai IndexedDB持久化原子重置为null,如何实现正确重水合?
嘿,我仔细看了你的代码,问题核心出在异步读取IndexedDB的逻辑没有和Jotai的状态更新机制联动上。你现在的readAtom只是发起了异步请求,但请求还没完成就直接返回了初始值(null),而且异步拿到数据后没有通知Jotai更新状态——这就导致页面加载时,atom一直停留在初始的null值,哪怕IndexedDB里明明有数据也没用。
问题拆解
原来的readAtom里,db.table.get()是异步操作,你在then里给storedValue赋值,但这时候readAtom已经返回了get(baseAtom)的结果(也就是初始的null)。Jotai的atom是同步计算的,不会因为后续storedValue的变化而重新触发计算,所以组件永远拿不到IndexedDB里存储的数据。
修复方案
我们需要让atom在初始化时主动从IndexedDB加载数据,并更新baseAtom的状态,这样Jotai就能自动通知组件重新渲染,拿到存储的值。修改后的atomWithIndexedDB实现如下:
export function atomWithIndexedDB<T>( key: string, initialValue: T ): WritableAtom<T, [T | ((prev: T) => T)], void> { // Base atom 保存本地状态 const baseAtom = atom(initialValue); function getUserIdFromCookie() { const match = Cookies.get('username'); return match ? match : null; } // 初始化时异步读取IndexedDB,更新baseAtom (async () => { try { const userId = getUserIdFromCookie(); let userScopedKey = `${key}`; if (!key.startsWith(`${userId}-`)) { userScopedKey = `${userId}-${key}`; } // 从IndexedDB获取存储的值 const storedValue = await db.table<AtomData<T>>('atoms').get(userScopedKey); if (storedValue) { // 更新baseAtom,触发组件重新渲染 baseAtom.setSelf(storedValue.value); } } catch (error) { console.error('初始化时读取IndexedDB失败:', error); } })(); // 读取逻辑直接返回baseAtom的值(会自动同步更新) const readAtom = atom((get) => get(baseAtom)); // 写入逻辑:同时更新baseAtom和IndexedDB const writeAtom = atom( null, async (get, set, update: T | ((prev: T) => T)) => { const newValue = typeof update === 'function' ? (update as (prev: T) => T)(get(baseAtom)) : update; set(baseAtom, newValue); // 保存到IndexedDB try { const userId = getUserIdFromCookie(); let userScopedKey = `${key}`; if (!key.startsWith(`${userId}-`)) { userScopedKey = `${userId}-${key}`; } await db.table<AtomData<T>>('atoms').put({ key: userScopedKey, value: newValue }); } catch (error) { console.error('写入IndexedDB失败:', error); } } ); // 同步读写的原子 const syncedAtom = atom( (get) => get(readAtom), (get, set, update: T | ((prev: T) => T)) => { set(writeAtom, update); } ); return syncedAtom; }
关键改动说明
- 初始化时主动加载数据:新增了一个自执行异步函数,在atom创建完成后立即从IndexedDB读取数据,拿到后用
baseAtom.setSelf()更新状态——这会触发所有使用该atom的组件重新渲染,从而获取到存储的数据。 - 简化读取逻辑:现在
readAtom直接返回baseAtom的值,因为baseAtom会被初始化的异步请求更新,组件一开始拿到初始值,等数据加载完成后会自动切换为存储的值。 - 修复key污染问题:原来的写入逻辑里直接修改了参数
key,这会导致后续调用的key被污染,现在改用局部变量userScopedKey避免这个问题。
额外优化建议
在useTranslation hook里,你可以处理一下translations未加载完成的状态,比如返回一个加载提示或者直接返回key:
export const useTranslation = () => { const [settingsAtom] = useAtom(SettingsConfigAtom); const [translations] = useAtom(TranslationDataAtom); const languageName = settingsAtom?.language?.name?.toLowerCase() || "english"; return function translate({ key }: { key: string }): string { // 未加载完成时返回key或自定义提示 if (!translations || !Array.isArray(translations)) return key; const entry = translations.find((item: { key: string }) => item.key === key); if (!entry || !entry.values || typeof entry.values[languageName] !== "string") { return key; } return entry.values[languageName]; }; };
这样修改后,页面刷新时,atom就会正确从IndexedDB重水合,translations会在数据加载完成后自动更新,你的翻译功能就能正常工作了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

