JotaiJotai

状態
Primitive and flexible state management for React

持久化

Jotai 在 utils bundle 中有一个 atomWithStorage 函数 用于持久化,支持在 sessionStoragelocalStorageAsyncStorage 或 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 yet
const 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 不要使用 Suspense
const 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>
);
};

示例