React 状态管理方案有哪些?如何选型?

一、为什么需要状态管理

React 组件通过 state 和 props 驱动 UI 渲染。当应用规模增长,状态管理面临以下挑战:

  • 跨层级共享:兄弟组件或深层嵌套组件间需要共享数据
  • 状态同步:多个组件依赖同一份数据,更新要保持一致
  • 服务端状态:API 数据的缓存、失效、乐观更新等复杂逻辑
  • 全局状态:主题、国际化、用户认证等应用级别的状态

不同的状态管理方案针对不同的问题场景,没有银弹。

二、React 内置方案

useState — 组件局部状态

最基础的状态管理,适用于组件内部的独立状态:

function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = async () => {
    const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
    setResults(data);
  };

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <button onClick={handleSearch}>搜索</button>
      <ResultList items={results} />
    </div>
  );
}

useReducer — 复杂局部状态

适用于多个相关联的状态字段、或复杂的更新逻辑:

function reducer(state, action) {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true, error: null };
    case 'FETCH_SUCCESS':
      return { loading: false, data: action.payload, error: null };
    case 'FETCH_ERROR':
      return { loading: false, data: null, error: action.payload };
    default:
      return state;
  }
}

function UserProfile({ userId }) {
  const [state, dispatch] = useReducer(reducer, {
    data: null, loading: true, error: null,
  });

  useEffect(() => {
    dispatch({ type: 'FETCH_START' });
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
      .catch(err => dispatch({ type: 'FETCH_ERROR', payload: err.message }));
  }, [userId]);

  if (state.loading) return <Spinner />;
  if (state.error) return <Error message={state.error} />;
  return <Profile user={state.data} />;
}

useContext — 跨层级传递

避免 prop drilling,适合低频变化的全局数据(主题、语言、当前用户):

const AuthContext = createContext(null);

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = async (credentials) => {
    const user = await api.login(credentials);
    setUser(user);
  };

  const logout = () => setUser(null);

  const value = useMemo(() => ({ user, login, logout }), [user]);

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be within AuthProvider');
  return ctx;
}

局限性:Context value 变化会触发所有消费者重渲染,不适合高频更新的状态。

三、Redux / Redux Toolkit

Redux 是最成熟的状态管理方案,通过单一 store、action、reducer 实现可预测的状态管理:

import { createSlice, configureStore } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: 0 },
  reducers: {
    addItem(state, action) {
      const existing = state.items.find(i => i.id === action.payload.id);
      if (existing) {
        existing.quantity += 1;
      } else {
        state.items.push({ ...action.payload, quantity: 1 });
      }
      state.total = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
    },
    removeItem(state, action) {
      state.items = state.items.filter(i => i.id !== action.payload);
      state.total = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
    },
  },
});

const store = configureStore({ reducer: { cart: cartSlice.reducer } });

优势:DevTools 时间旅行调试、中间件生态、selector 优化、RTK Query。

劣势:对小型项目来说过于重量级,概念多。

四、Zustand

Zustand 是一个轻量级(~1KB)的状态管理库,API 极其简洁:

import { create } from 'zustand';

const useStore = create((set, get) => ({
  count: 0,
  todos: [],
  increment: () => set(state => ({ count: state.count + 1 })),
  addTodo: (text) => set(state => ({
    todos: [...state.todos, { id: Date.now(), text, done: false }],
  })),
  toggleTodo: (id) => set(state => ({
    todos: state.todos.map(t =>
      t.id === id ? { ...t, done: !t.done } : t
    ),
  })),
  get totalDone() {
    return get().todos.filter(t => t.done).length;
  },
}));

function Counter() {
  const count = useStore(state => state.count);
  const increment = useStore(state => state.increment);
  return <button onClick={increment}>{count}</button>;
}

function TodoList() {
  const todos = useStore(state => state.todos);
  const toggleTodo = useStore(state => state.toggleTodo);
  return (
    <ul>
      {todos.map(t => (
        <li key={t.id} onClick={() => toggleTodo(t.id)}>{t.text}</li>
      ))}
    </ul>
  );
}

特点

  • 不需要 Provider 包裹
  • 基于 selector 的精确订阅,天然避免不必要的重渲染
  • 支持中间件(persist、devtools、immer)
  • API 简单直觉
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        user: null,
        setUser: (user) => set(state => { state.user = user; }),
      })),
      { name: 'app-store' }
    )
  )
);

五、Jotai

Jotai 采用原子化(atomic)的状态管理模型,灵感来自 Recoil 但更轻量:

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';

const countAtom = atom(0);
const doubledAtom = atom(get => get(countAtom) * 2);
const todosAtom = atom([]);

const filteredTodosAtom = atom(get => {
  const todos = get(todosAtom);
  const filter = get(filterAtom);
  if (filter === 'done') return todos.filter(t => t.done);
  if (filter === 'active') return todos.filter(t => !t.done);
  return todos;
});

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const doubled = useAtomValue(doubledAtom);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <span>doubled: {doubled}</span>
    </div>
  );
}

特点:自底向上的原子模型、派生 atom 自动追踪依赖、天然支持异步、极小的 bundle size。

六、MobX

MobX 基于响应式编程,通过 observable、computed、action 实现自动追踪和更新:

import { makeAutoObservable, runInAction } from 'mobx';
import { observer } from 'mobx-react-lite';

class TodoStore {
  todos = [];
  filter = 'all';

  constructor() {
    makeAutoObservable(this);
  }

  get filtered() {
    if (this.filter === 'done') return this.todos.filter(t => t.done);
    if (this.filter === 'active') return this.todos.filter(t => !t.done);
    return this.todos;
  }

  addTodo(text) {
    this.todos.push({ id: Date.now(), text, done: false });
  }

  async fetchTodos() {
    const data = await fetch('/api/todos').then(r => r.json());
    runInAction(() => {
      this.todos = data;
    });
  }
}

const store = new TodoStore();

const TodoList = observer(function TodoList() {
  return (
    <ul>
      {store.filtered.map(t => (
        <li key={t.id}>{t.text}</li>
      ))}
    </ul>
  );
});

特点:最少的样板代码,可变数据模型,自动精确更新。但"魔法"太多,调试不够透明。

七、方案对比

维度ContextRedux/RTKZustandJotaiMobX
Bundle 大小0(内置)~11KB~1KB~3KB~16KB
学习曲线
样板代码中(RTK 后减少)极少极少
DevTools优秀支持支持支持
精确订阅selectorselector原子粒度自动追踪
中间件丰富支持插件
SSR 支持一般
TypeScript
适用规模中大中小中小

八、选型建议

按项目规模

小型项目(个人/原型)
  → useState + useContext 足矣

中型项目(团队协作)
  → Zustand 或 Jotai(轻量、简洁、够用)
  → 需要规范化流程 → Redux Toolkit

大型项目(企业级)
  → Redux Toolkit + RTK Query(成熟生态、规范化、DevTools)
  → 或 Zustand + React Query(分离客户端和服务端状态)

按状态类型

客户端 UI 状态(表单、模态框、主题)
  → useState / Zustand / Jotai

服务端状态(API 数据、缓存)
  → RTK Query / React Query / SWR

全局应用状态(购物车、用户认证)
  → Redux Toolkit / Zustand

跨组件临时状态(拖拽位置、选中项)
  → Zustand / Jotai

关键原则

  1. 区分客户端状态和服务端状态:服务端数据用专门的请求库(RTK Query / React Query)
  2. 就近管理:状态尽量放在使用它的组件附近,需要共享时才提升
  3. 不要过度设计:从 useState 开始,遇到瓶颈再引入工具
  4. 团队一致性:选定方案后在项目中保持统一

九、总结

状态管理不是"选最好的"问题,而是"选最合适的"。理解每个方案的适用边界,根据项目需求和团队经验做出选择。