Skip to content

状态管理选型决策

状态分类、四库核心对比、Context 边界、决策树与面试高频题

一、状态分类

不同类型的状态应使用不同的方案,不要用一个库管所有状态。

状态类型特点推荐方案
局部 UI 状态只在单个组件内使用useState / useReducer
跨组件共享多组件需要读写同一状态Context / Zustand / Jotai
服务端状态来自 API,需要缓存和同步TanStack Query / SWR
URL 状态需要序列化到 URLRouter searchParams
表单状态复杂验证和多步骤React Hook Form
全局复杂状态大型应用全局状态Zustand / Redux Toolkit

二、四库核心对比

维度ZustandJotaiRedux ToolkitValtio
心智模型单 store(自上而下)原子化(自下而上)单 store + sliceProxy 响应式
Provider❌ 不需要❌ 不需要✅ 必须❌ 不需要
更新方式set 函数setAtomdispatch(action)直接修改对象
派生状态手动 selector / 计算atom 原生支持createSelectorderive(第三方)
异步直接 async/await异步 atom + SuspensecreateAsyncThunk直接 async/await
Immer中间件可选不需要内置不需要
DevTools中间件接入插件内置最强插件
持久化persist 中间件atomWithStorage手动 / RTK Query手动
包体积~1KB~2KB~11KB~3KB
TypeScript优秀优秀优秀良好
学习曲线极低中等极低
适合场景中小型通用细粒度状态 / 复杂派生大型团队 / 严格规范Vue 背景 / 快速原型

三、同一需求的四种写法对比

计数器

jsx
// Zustand
const useStore = create((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}));
function Counter() {
  const count = useStore((s) => s.count);
  const increment = useStore((s) => s.increment);
  return <button onClick={increment}>{count}</button>;
}

// Jotai
const countAtom = atom(0);
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

// Redux Toolkit
const slice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: { increment: (state) => { state.count++; } },
});
function Counter() {
  const count = useSelector((s) => s.counter.count);
  const dispatch = useDispatch();
  return <button onClick={() => dispatch(slice.actions.increment())}>{count}</button>;
}

// Valtio
const state = proxy({ count: 0 });
function Counter() {
  const snap = useSnapshot(state);
  return <button onClick={() => { state.count++; }}>{snap.count}</button>;
}

可以看到:

  • Zustand:最接近 React 的 useState 心智模型
  • Jotai:代码最少,原子级定义
  • Redux Toolkit:最规范,但步骤最多
  • Valtio:最直觉,直接修改对象

四、Context 的正确使用边界

Context 不是"状态管理工具",而是"依赖注入机制"。

jsx
// ✅ Context 适合:低频变化、全局配置
const ThemeContext = createContext('light');
const I18nContext = createContext('zh-CN');
const AuthContext = createContext(null);

// ❌ Context 不适合:高频更新
// Context 值变化 → 所有消费者重渲染(无选择器)
const FormContext = createContext({ /* 频繁变化的表单数据 */ });

// 如果必须用 Context 传递状态,优化策略:
// 1. 拆分 Context(按更新频率分离)
// 2. useMemo 包裹 Provider value
// 3. 状态和 dispatch 分成两个 Context

五、选型决策树

你需要什么?

  ├── 组件内部状态 → useState / useReducer

  ├── 少量跨组件共享 + 低频更新 → Context

  ├── API 数据(缓存、同步、后台刷新) → TanStack Query / SWR

  ├── 复杂表单 → React Hook Form

  ├── 客户端全局状态 →
  │     │
  │     ├── 想要最简单的 API → Zustand
  │     │
  │     ├── 有大量派生计算 / 原子化需求 → Jotai
  │     │
  │     ├── 大型团队 / 需要严格规范 / 强 DevTools → Redux Toolkit
  │     │
  │     ├── 喜欢可变风格 / Vue 背景 → Valtio
  │     │
  │     └── 不确定 → Zustand(最通用、最简单、社区最活跃)

  └── 以上组合使用(常见)
        例:Zustand(UI 状态)+ TanStack Query(API 数据)+ React Hook Form(表单)

六、按项目规模推荐

小型项目(个人 / 小团队)

useState + Context + TanStack Query

Zustand + TanStack Query
  • 状态简单,不需要额外库
  • TanStack Query 管理 API 数据

中型项目(5-15 人团队)

Zustand(客户端状态)+ TanStack Query(服务端状态)+ React Hook Form(表单)
  • Zustand 简洁够用,新人上手快
  • 服务端状态和客户端状态分离

大型项目(15+ 人 / 微前端)

Redux Toolkit(全局状态)+ RTK Query 或 TanStack Query(API)+ React Hook Form
  • Redux 的严格规范保证代码一致性
  • DevTools 在复杂调试中不可替代
  • 微前端场景可通过 Redux 共享状态

面试高频题

Q: 为什么不推荐用 Context 做高频更新的状态管理?

Context 没有选择器机制,值变化时所有消费者都重渲染,无论是否用到了变化的部分。而 Zustand、Jotai 等库通过选择器或原子化实现细粒度订阅,只有用到的值变化时才重渲染。

Q: 服务端状态和客户端状态有什么区别?

  • 服务端状态:来自 API,有缓存、过期、同步、竞态、乐观更新等需求。用 TanStack Query 等专用工具
  • 客户端状态:只存在于前端,如 UI 状态、表单状态。用 useState / Zustand 等

把两者混在一个 store 里管理(如用 Redux 管 API 数据)是常见的反模式。

Q: Zustand、Jotai、Redux Toolkit、Valtio 怎么选?

需求推荐
最简单、最快上手Zustand
大量派生计算Jotai
大型团队 + 强调规范Redux Toolkit
可变风格 / Vue 背景Valtio
不确定Zustand

Q: 一个项目可以混用多个状态管理库吗?

可以且推荐。按场景选型:

  • useState / useReducer:局部状态
  • Zustand / Jotai:跨组件的客户端状态
  • TanStack Query:服务端数据缓存
  • React Hook Form:表单
  • URL searchParams:URL 状态

它们各自管理不同类型的状态,不会冲突。