先分清两种状态
客户端状态 (Client State) 服务器状态 (Server State)
- UI 开关 / 弹窗 - API 返回的数据
- 表单临时输入 - 缓存、重试、过期
- 当前用户偏好 - SSR/CSR hydration
→ useState/useReducer/Context → TanStack Query / SWR
/Zustand/Jotai/Redux 或 RSC 直接 await
这是 2024+ 最大的认知更新:API 数据不要塞进 Redux/Zustand——用专门的 server state 库。
客户端状态选型决策树
状态在一个组件用?
├─ 是 → useState
└─ 否 → 多个组件用?
├─ 是父子 → 提升到共同父级(lifting up)
└─ 跨多层 / 兄弟 → 频繁变化?
├─ 否 → Context(主题 / 当前用户)
└─ 是 → 状态结构复杂?
├─ 否 → Zustand / Jotai
└─ 是 → Redux Toolkit
各库一句话定位
useState
- 一个组件 / 一对父子用
- React 自带,零依赖
- 90% 场景够用
Context + useReducer
- 跨多层、变化不频繁
- 自带,零额外依赖
- 缺点:Provider value 变 → 所有消费者 re-render
Zustand
- 跨组件、变化频繁、想保持简单
- API 极简,支持选择器订阅(只读你订阅的字段 re-render)
- 推荐为"默认选项"
import { create } from 'zustand';
const useCart = create<{
items: Item[];
add: (item: Item) => void;
}>((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
}));
// 组件里
const items = useCart(s => s.items); // 只订阅 items
const add = useCart(s => s.add);
Jotai
- 原子化状态(atom)模型
- 适合多个小、独立的状态片段
- 心智模型与 Recoil / Signal 接近
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const [count, setCount] = useAtom(countAtom);
Redux Toolkit
- 大型应用 / 团队需要严格规范
- 内置 DevTools、时间旅行调试
- 配合 RTK Query 做 server state
- 新项目慎选——Zustand 在多数场景够用而且更简单
Valtio / Signals / 其他
- Valtio:proxy 化的可变写法,Zustand 同公司出品
- 各种 Signals 库(@preact/signals-react):细粒度响应
- Recoil 已被 Meta 归档(2025 年初)——新项目别选,已用的项目逐步迁出(Jotai 思路最接近)
服务器状态:用专门的
import { useQuery } from '@tanstack/react-query';
const { data, isLoading, error } = useQuery({
queryKey: ['user', id],
queryFn: () => fetch(`/api/user/${id}`).then(r => r.json()),
});
它自动处理:
- 缓存(同样的 key 多个组件共享)
- 重试(失败自动重试 3 次)
- 过期重新拉(窗口聚焦时)
- 并发去重
- 乐观更新
- SSR hydration
自己用 useEffect + fetch 写不到一半就发现要处理这堆问题。直接用库。
RSC 时代的转变
Next.js App Router 让"server 状态"变得:
async function Page() {
const user = await db.user.findUnique({ where: { id } });
return <UserCard user={user} />;
}
Server Component 直接 await,不需要客户端状态管理库。客户端组件还有的就是真正的"客户端交互状态"——选 Zustand 就行。
选型对照
| 情况 | 用 |
|---|---|
| 单组件 UI 状态 | useState |
| 父子组件共享 | 提升到父 |
| 主题 / 当前用户 / 语言 | Context |
| 表单数据 | React Hook Form |
| 客户端复杂状态(购物车 / wizard) | Zustand |
| API 返回的数据 | TanStack Query / SWR |
| 服务端数据 + Next.js | 直接在 Server Component await |
| 大型应用 / 已有 Redux | Redux Toolkit |
| 原子化 / 想换种思路 | Jotai |
→ 下一篇 数据获取范式