前言
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 | 低频更新,无性能压力 |
| 表单状态、购物车 | Zustand | API 最简,细粒度订阅 |
| 复杂派生状态(搜索、过滤) | Jotai | 原子依赖自动追踪 |
| 服务端状态(API 数据) | TanStack Query | 缓存、重试、乐观更新内置 |
| 全局用户认证 | Zustand | 持久化 + DevTools 开箱即用 |
核心原则:不要为了"统一"而用一个方案处理所有状态。服务器数据用 TanStack Query,客户端状态用 Zustand/Jotai,UI 状态用 Context。各司其职才是最优雅的架构。