先分清两种状态

客户端状态 (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

→ 下一篇 数据获取范式