用途
Jotai X 是一个基于 Jotai 的状态管理方案,围绕 createAtomStore 创建 store,并生成一组配套的 Provider 与 hook。它主要用于创建和消费 Jotai X store、在组件或回调中访问状态,以及把状态持久化到 cookie 或 localStorage。其核心目标是:最小化订阅、最大化性能。
核心原则
只有当组件需要在值变化时重新渲染,才使用 hook(useValue 类)订阅;其它情况一律使用 store 方法(store.get、store.set)直接读写。
// ✅ 只有在组件需要因变化重新渲染时才用 hook 订阅
const count = useAppValue("count");
// ✅ 在回调/处理器中读取而不订阅
const handleClick = () => {
const count = store.get("count");
console.log(count);
};
// ✅ 在回调/处理器中写入
const handleIncrement = () => {
store.set("count", (prev) => prev + 1);
};
决策指南
读取状态:
- 需要组件在值变化时重新渲染 → useAppValue('key')
- 只需要一次性读取当前值 → store.get('key')
- 在回调/事件处理器中 → store.get('key')
- 在 useEffect 中 → 通常用 store.get('key'),除非需要作为依赖
写入状态:所有情况 → store.set('key', value)
同时需要读与写:不要使用 useAppState 或 useAppSet,改用上述组合。
速查表
| 场景 | 使用 | 不要使用 |
|---|---|---|
| 组件需要响应式 | useAppValue('key') | store.get('key') |
| 回调中一次读取 | store.get('key') | useAppValue('key') |
| 事件处理器读取 | store.get('key') | useAppValue('key') |
| 任何写入操作 | store.set('key', val) | useAppSet('key') |
| 需要读+写 | useAppValue + store.set | useAppState |
API 参考
创建 store
import { createAtomStore } from "jotai-x";
export const {
useChatStore,
useChatValue,
ChatProvider,
} = createAtomStore(
{
count: 0,
name: "Alice",
items: [] as string[],
},
{
name: "chat",
}
);
store 实例 API
const store = useChatStore();
store.get("count");
store.getCount();
store.set("count", 5);
store.set("count", (c) => c + 1);
store.setCount(5);
const unsub = store.subscribe("count", (value) => {
console.log(value);
});
Hook API(尽量少用)
const count = useChatValue("count");
const firstItem = useChatValue(
"items",
{
selector: (items) => items[0],
},
[]
);
const items = useChatValue(
"items",
{
equalityFn: (a, b) => a.length === b.length,
},
[]
);
常见错误
- 在回调中使用 hook:会创建不必要的订阅,并可能读到过期闭包值;应改用 store.get。
- 使用 useAppState:属于过度订阅;应使用 useChatValue 订阅,并用 store.set 写入。
- 使用 useAppSet:不必要的 hook 调用;应直接用 store.set。
- 过度订阅:组件订阅了不参与渲染的值,应在需要时用 store.get 获取。
性能影响
不良模式中,组件订阅了 4 个 atom 但只在渲染中使用 2 个,任一变化都会触发重渲染;良好模式只订阅渲染所需的值,其余在回调中通过 store.get 获取。
持久化
在 store 定义中直接使用 atomWithCookie 或 atomWithLocalStorage 即可把值持久化到 cookie 或 localStorage。
存储选择:
- Cookie:需要服务端访问、SSR 水合、小数据。
- LocalStorage:仅客户端、较大数据、无需 SSR。
atomWithCookie(对 SSR 友好):
import { atomWithCookie } from "@/lib/utils/atomWithCookie";
export const { useChatStore, useChatValue, ChatProvider } = createAtomStore(
{
isOpen: atomWithCookie("chat_isOpen", true),
},
{ name: "chat" }
);
服务端水合:
import { cookies } from "next/headers";
import { getCookieParser } from "@/lib/utils/getCookieParser";
export async function Layout({ children }) {
const cookieStore = await cookies();
const parser = getCookieParser(cookieStore);
const isOpen = parser.boolean("chat_isOpen") ?? true;
return <ChatProvider initialValues={{ isOpen }}>{children}</ChatProvider>;
}
Cookie 命名约定:prefix_key(如 chat_isOpen、chat_mode)。
atomWithLocalStorage(仅客户端):
import { atomWithLocalStorage } from "@/lib/utils/atomWithLocalStorage";
export const { useAppStore, useAppValue, AppProvider } = createAtomStore(
{
theme: atomWithLocalStorage("app_theme", "light"),
},
{ name: "app" }
);
无需服务端水合,因为 localStorage 仅存在于客户端。
持久化常见错误:不要通过 extend 选项来实现持久化;应直接赋值 atomWithCookie / atomWithLocalStorage;使用 cookie 时不要忘记服务端水合,否则首屏会忽略 cookie 值。