前端状态管理方案对比
状态管理的本质
前端状态管理要解决的问题是:如何让多个组件共享和同步数据。随着应用规模增长,组件之间的通信变得复杂,状态管理工具应运而生。
状态分类
本地状态 vs 全局状态
// 本地状态:组件内部使用
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// 全局状态:多个组件共享
// 需要状态管理工具
服务端状态 vs 客户端状态
- 服务端状态:来自 API 的数据,需要缓存、同步、刷新
- 客户端状态:UI 状态(主题、侧边栏展开/收起)、表单输入
主流方案对比
| 方案 | 适用场景 | 学习曲线 | Bundle 大小 | 特点 |
|---|---|---|---|---|
| Redux / Redux Toolkit | 大型复杂应用 | 高 | ~12KB | 可预测、中间件生态丰富 |
| Zustand | 中小型应用 | 低 | ~1KB | 简洁、TypeScript 友好 |
| Pinia (Vue) | Vue 3 项目 | 低 | ~1KB | Vue 官方推荐 |
| Jotai | React 原子化 | 中 | ~3KB | 原子模型、自然异步 |
| React Context | 低频更新 | 低 | 0 | 内置、无额外依赖 |
| TanStack Query | 服务端状态 | 中 | ~13KB | 缓存、自动刷新、SSR |
| Valtio | 响应式代理 | 中 | ~2KB | Proxy 代理、可变更新 |
Redux Toolkit
核心概念
Redux 基于单一数据源(store)、只读状态(通过 dispatch action 修改)、纯函数 reducer。
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
// 创建 slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
decrement: (state) => { state.value -= 1; },
setValue: (state, action: PayloadAction<number>) => { state.value = action.payload; },
},
});
// 创建 store
const store = configureStore({
reducer: { counter: counterSlice.reducer },
});
// 使用
store.dispatch(counterSlice.actions.increment());
console.log(store.getState().counter.value);
export const { increment, decrement } = counterSlice.actions;
export default store;
React 集成
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return <button onClick={() => dispatch(increment())}>{count}</button>;
}
Zustand
Zustand 的 API 极简,不需要 Provider 包裹。
import { create } from 'zustand';
interface BearStore {
bears: number;
increase: () => void;
reset: () => void;
}
const useStore = create<BearStore>((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
reset: () => set({ bears: 0 }),
}));
// 在组件中使用
function BearCounter() {
const bears = useStore((state) => state.bears);
const increase = useStore((state) => state.increase);
return (
<div>
<p>{bears} 只熊</p>
<button onClick={increase}>增加</button>
</div>
);
}
Zustand 中间件
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
theme: 'light',
setTheme: (theme: string) => set({ theme }),
}),
{ name: 'theme-storage' }
)
);
Jotai(原子化)
Jotai 采用原子模型,每个 atom 是独立的状态单元,通过组合构建复杂状态。
import { atom, useAtom } from 'jotai';
// 基础 atom
const countAtom = atom(0);
// 派生 atom(类似 computed)
const doubleAtom = atom((get) => get(countAtom) * 2);
// 异步 atom
const userAtom = atom(async () => {
const res = await fetch('/api/user');
return res.json();
});
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [double] = useAtom(doubleAtom);
return (
<div>
<p>{count} × 2 = {double}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
}
Pinia(Vue 3)
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: null as string | null,
}),
getters: {
isLoggedIn: (state) => !!state.token,
greeting: (state) => `你好, ${state.name}!`,
},
actions: {
async login(username: string, password: string) {
const { token, name } = await api.login(username, password);
this.token = token;
this.name = name;
},
logout() {
this.token = null;
this.name = '';
},
},
});
服务端状态管理:TanStack Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function TodoList() {
const queryClient = useQueryClient();
const { data, isLoading } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await fetch('/api/todos');
return res.json();
},
});
const addMutation = useMutation({
mutationFn: async (newTodo) => {
return fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo) });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] }); // 自动刷新
},
});
if (isLoading) return <div>加载中...</div>;
return (
<div>
{data?.map(todo => <div key={todo.id}>{todo.text}</div>)}
<button onClick={() => addMutation.mutate({ text: '新任务' })}>添加</button>
</div>
);
}
选型建议
// 选型决策树:
// 1. 主要是服务端数据缓存?
// → TanStack Query / SWR / RTK Query
// 2. 小型应用,少量全局状态?
// → React Context / Vue provide-inject
// 3. 中小型应用,需要简洁方案?
// → Zustand(React) / Pinia(Vue)
// 4. 大型应用,需要严格的数据流?
// → Redux Toolkit
// 5. 喜欢原子化、组合式思维?
// → Jotai / Recoil
// 6. 需要状态持久化?
// → Zustand + persist 中间件 / Redux Toolkit + redux-persist
总结
没有最好的状态管理方案,只有最合适的。推荐策略:服务端状态用 TanStack Query,客户端全局状态用 Zustand 或 Redux Toolkit,简单场景用 React Context。避免过度设计,只在真正需要时引入全局状态管理。