Develop

React 状态管理进阶:Context、Zustand 与 Jotai 选型实战

✎ -- 字 🕐 -- 分钟
字号
状态管理方案对比Context + useReducer✅ 零依赖⚠️ 性能陷阱适用: 主题/语言/认证不适: 高频更新数据Zustand✅ 极简 API✅ 细粒度订阅✅ DevTools / 持久化适用: 中小型应用首选Jotai✅ 原子化✅ 派生状态✅ React 原生心智适用: 复杂状态依赖

前言

React 生态的状态管理方案百花齐放,从 Redux 到 MobX,从 Recoil 到 Jotai。本文聚焦当前最主流的三种方案——Context + useReducer、Zustand、Jotai,通过真实场景对比它们的实际表现。

一、Context + useReducer:React 原生的双刃剑

// 创建 Context + Reducer
import { createContext, useContext, useReducer, ReactNode } from 'react';

// State 定义
interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: number;
}

// Action 定义
type AppAction =
  | { type: 'SET_USER'; payload: AppState['user'] }
  | { type: 'TOGGLE_THEME' }
  | { type: 'SET_NOTIFICATIONS'; payload: number };

const AppContext = createContext<{
  state: AppState;
  dispatch: React.Dispatch<AppAction>;
} | null>(null);

function appReducer(state: AppState, action: AppAction): AppState {
  switch (action.type) {
    case 'SET_USER': return { ...state, user: action.payload };
    case 'TOGGLE_THEME': return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_NOTIFICATIONS': return { ...state, notifications: action.payload };
    default: return state;
  }
}

// ⚠️ 性能陷阱:任何 state 变化都会让所有消费者重新渲染
// 解决方案:拆分成多个 Context
const ThemeContext = createContext<AppState['theme']>('light');
const UserContext = createContext<AppState['user']>(null);

二、Zustand:极简主义的胜利(推荐)

import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

interface BearStore {
  bears: number;
  increase: (by?: number) => void;
  reset: () => void;
}

const useBearStore = create<BearStore>()(
  devtools(
    persist(
      (set) => ({
        bears: 0,
        increase: (by = 1) => set((state) => ({ bears: state.bears + by })),
        reset: () => set({ bears: 0 }),
      }),
      { name: 'bear-storage' } // 自动持久化到 localStorage
    ),
    { name: 'BearStore' } // DevTools 中显示
  )
);

// 使用——自动细粒度订阅,无额外渲染
function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears} bears</h1>;
}

function BearControls() {
  const increase = useBearStore((state) => state.increase);
  // 这里不会因为 bears 变化而重渲染!
  return <button onClick={() => increase()}>+1</button>;
}

// 高级技巧:派生状态
const useBearStats = () =>
  useBearStore((state) => ({
    count: state.bears,
    hasMany: state.bears > 5,
    status: state.bears === 0 ? 'empty' : state.bears > 10 ? 'crowded' : 'normal',
  }));

Zustand 实战:用户认证 Store

interface AuthStore {
  user: { id: string; name: string } | null;
  token: string | null;
  login: (username: string, password: string) => Promise<void>;
  logout: () => void;
}

const useAuthStore = create<AuthStore>()((set) => ({
  user: null,
  token: null,
  login: async (username, password) => {
    const res = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ username, password }),
    });
    const data = await res.json();
    set({ user: data.user, token: data.token });
  },
  logout: () => set({ user: null, token: null }),
}));

三、Jotai:原子化的状态哲学

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

// 基础原子
const countAtom = atom(0);
const doubledAtom = atom((get) => get(countAtom) * 2); // 派生(只读)

// 组件中使用——最小的重渲染范围
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

function DoubledDisplay() {
  const doubled = useAtomValue(doubledAtom); // 只读
  return <div>Doubled: {doubled}</div>;
}

// 持久化原子
const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light');

// 复杂组合:搜索 + 过滤
const allItemsAtom = atom<Item[]>([]);
const searchQueryAtom = atom('');
const filteredItemsAtom = atom((get) => {
  const query = get(searchQueryAtom).toLowerCase();
  return get(allItemsAtom).filter(item => item.name.toLowerCase().includes(query));
});

四、场景选型指南

场景推荐方案原因
主题切换、语言切换Context低频更新,无性能压力
表单状态、购物车ZustandAPI 最简,细粒度订阅
复杂派生状态(搜索、过滤)Jotai原子依赖自动追踪
服务端状态(API 数据)TanStack Query缓存、重试、乐观更新内置
全局用户认证Zustand持久化 + DevTools 开箱即用

核心原则:不要为了"统一"而用一个方案处理所有状态。服务器数据用 TanStack Query,客户端状态用 Zustand/Jotai,UI 状态用 Context。各司其职才是最优雅的架构。