持久化
Jotai 在 utils bundle 中有一个 atomWithStorage 函数 用于持久化,支持在 sessionStorage
、localStorage
、AsyncStorage
或 URL 哈希中持久化状态 .
这里还有几个替代实现:
localStorage 的简单模式
const strAtom = atom(localStorage.getItem("myKey") ?? "foo");const strAtomWithPersistence = atom((get) => get(strAtom),(get, set, newStr) => {set(strAtom, newStr);localStorage.setItem("myKey", newStr);});
具有 localStorage 和 JSON 解析的辅助函数
const atomWithLocalStorage = (key, initialValue) => {const getInitialValue = () => {const item = localStorage.getItem(key);if (item !== null) {return JSON.parse(item);}return initialValue;};const baseAtom = atom(getInitialValue());const derivedAtom = atom((get) => get(baseAtom),(get, set, update) => {const nextValue =typeof update === "function" ? update(get(baseAtom)) : update;set(baseAtom, nextValue);localStorage.setItem(key, JSON.stringify(nextValue));});return derivedAtom;};
(应添加错误处理。)
带有 AsyncStorage 和 JSON 解析的辅助函数
这需要 onMount。
const atomWithAsyncStorage = (key, initialValue) => {const baseAtom = atom(initialValue);baseAtom.onMount = (setValue) => {(async () => {const item = await AsyncStorage.getItem(key);setValue(JSON.parse(item));})();};const derivedAtom = atom((get) => get(baseAtom),(get, set, update) => {const nextValue =typeof update === "function" ? update(get(baseAtom)) : update;set(baseAtom, nextValue);AsyncStorage.setItem(key, JSON.stringify(nextValue));});return derivedAtom;};
不要忘记查看 异步文档 以获取有关如何使用异步原子的更多详细信息。
在异步操作中的用法
使用异步存储时,创建的原子的读取也是异步的。 这有一些限制。 在至少调用一次 useAtom
之前,未设置该值。
这可能会导致问题,例如,如果您想在异步操作(只写原子)中检索存储的原子的值。
让我们使用 utils 包中的 atomWithStorage 函数 作为示例,以便我们有更多可用选项。
import { atomWithStorage, createJSONStorage } from "jotai/utils";const storage = createJSONStorage(() => AsyncStorage);const userId = atomWithStorage("user-id-key", null, storage);const userInfo = atom({});const fetchUserInfo = atom(null, async (get, set, payload: any) => {const id = get(userId); // This throws an error if "userId" is not loaded yetconst info = await fetch("https://jotai.org/users/" + id);set(userInfo, info);});
我们要确保行动永远不会失败。 与 Jotai 中的异步流程一样,我们有 2 个选择:有或没有 Suspense。
Suspense
您可能希望直接在应用程序的根级别预加载原子:
const Preloader = () => {useAtomValue(userId); // 触发将从 store 加载数据的“onMount”函数return null;};
const Root = () => {return (<Suspense fallback={<Text>Hydrating...<Text>}><Preloader /> {/* Wait for atoms to preload */}<App /> {/* Rest of your app */}</Suspense>)}
没有 Suspense
// 我们添加了“delayInit: true”选项来告诉 jotai 不要使用 Suspenseconst storage = { ...createJSONStorage(() => AsyncStorage), delayInit: true };const userId = atomWithStorage("user-id-key", null, storage);const userInfo = atom({});const fetchUserInfo = atom(null, async (get, set, payload) => {let id = get(userId);if (id === null) {// 如果尚未加载任何值,我们确保预加载id = await AsyncStorage.getItem("user-id-key");}const info = await fetch("https://jotai.org/users/" + id);set(userInfo, info);});
但永远记住 jotai 是不拘一格的,你的工作流程可能根本不需要预加载。
sessionStorage 示例
与 AsyncStorage 相同,只需使用 atomWithStorage
util 并使用 sessionStorage
覆盖默认存储
import { atomWithStorage, createJSONStorage } from "jotai/utils";const storage = createJSONStorage(() => sessionStorage);const someAtom = atomWithStorage("some-key", someInitialValue, storage);
序列化原子模式
const serializeAtom = atom<null,| { type: "serialize"; callback: (value: string) => void }| { type: "deserialize"; value: string }>(null, (get, set, action) => {if (action.type === "serialize") {const obj = {todos: get(todosAtom).map(get),};action.callback(JSON.stringify(obj));} else if (action.type === "deserialize") {const obj = JSON.parse(action.value);// 需要错误处理和类型检查set(todosAtom,obj.todos.map((todo: Todo) => atom(todo)));}});const Persist = () => {const [, dispatch] = useAtom(serializeAtom);const save = () => {dispatch({type: "serialize",callback: (value) => {localStorage.setItem("serializedTodos", value);},});};const load = () => {const value = localStorage.getItem("serializedTodos");if (value) {dispatch({ type: "deserialize", value });}};return (<div><button onClick={save}>Save to localStorage</button><button onClick={load}>Load from localStorage</button></div>);};
示例
带有 atomFamily 的模式
const serializeAtom = atom<null,| { type: "serialize"; callback: (value: string) => void }| { type: "deserialize"; value: string }>(null, (get, set, action) => {if (action.type === "serialize") {const todos = get(todosAtom);const todoMap: Record<string, { title: string; completed: boolean }> = {};todos.forEach((id) => {todoMap[id] = get(todoAtomFamily({ id }));});const obj = {todos,todoMap,filter: get(filterAtom),};action.callback(JSON.stringify(obj));} else if (action.type === "deserialize") {const obj = JSON.parse(action.value);// 需要错误处理和类型检查set(filterAtom, obj.filter);obj.todos.forEach((id: string) => {const todo = obj.todoMap[id];set(todoAtomFamily({ id, ...todo }), todo);});set(todosAtom, obj.todos);}});const Persist = () => {const [, dispatch] = useAtom(serializeAtom);const save = () => {dispatch({type: "serialize",callback: (value) => {localStorage.setItem("serializedTodos", value);},});};const load = () => {const value = localStorage.getItem("serializedTodos");if (value) {dispatch({ type: "deserialize", value });}};return (<div><button onClick={save}>Save to localStorage</button><button onClick={load}>Load from localStorage</button></div>);};