状态管理全景
前端状态管理是架构设计的核心。选错工具会让代码复杂度爆炸,选对工具让开发如鱼得水。
状态分类
前端状态的四种类型:
1. 本地 UI 状态(Local UI State)
- 模态框开关、表单输入、hover 状态
- 工具:useState、useReducer
- 原则:尽量本地化,不要提升到全局
2. 共享客户端状态(Shared Client State)
- 用户偏好、主题、购物车、认证信息
- 工具:Zustand、Jotai、Pinia(Vue)
- 原则:只共享真正需要跨组件的状态
3. 服务端状态(Server State)
- API 数据、分页、缓存
- 工具:TanStack Query、SWR
- 原则:不要把服务端数据放进客户端状态管理
4. URL 状态(URL State)
- 搜索参数、过滤条件、当前页
- 工具:React Router、Next.js Router
- 原则:可分享、可书签的状态放 URL工具选型对比
| 工具 | 适用场景 | 特点 |
|---|---|---|
| useState/useReducer | 组件本地状态 | 内置,零依赖 |
| Context API | 低频更新的全局状态 | 内置,但性能有限 |
| Zustand | React 全局状态 | 极简 API,高性能 |
| Jotai | 原子化状态 | 细粒度更新 |
| Redux Toolkit | 复杂企业应用 | 规范严格,DevTools 强大 |
| TanStack Query | 服务端状态 | 缓存、同步、后台更新 |
| Pinia | Vue 全局状态 | Vue 官方推荐 |
Zustand
tsx
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
interface CartStore {
items: CartItem[]
total: number
addItem: (product: Product) => void
removeItem: (id: string) => void
clearCart: () => void
}
const useCartStore = create<CartStore>()(
devtools(
persist(
immer((set, get) => ({
items: [],
total: 0,
addItem: (product) => set(state => {
const existing = state.items.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
state.items.push({ ...product, quantity: 1 })
}
state.total = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
}),
removeItem: (id) => set(state => {
state.items = state.items.filter(i => i.id !== id)
state.total = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
}),
clearCart: () => set({ items: [], total: 0 })
})),
{ name: 'cart-storage' } // localStorage 持久化
)
)
)
// 使用:选择性订阅(避免不必要的重渲染)
function CartIcon() {
const count = useCartStore(state => state.items.length) // 只订阅 count
return <span>{count}</span>
}
function CartTotal() {
const total = useCartStore(state => state.total) // 只订阅 total
return <span>¥{total.toFixed(2)}</span>
}Jotai(原子化状态)
tsx
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
// 原子:最小状态单元
const countAtom = atom(0)
const textAtom = atom('')
// 派生原子(computed)
const doubledAtom = atom(get => get(countAtom) * 2)
// 可写派生原子
const uppercaseAtom = atom(
get => get(textAtom).toUpperCase(),
(get, set, value: string) => set(textAtom, value.toLowerCase())
)
// 持久化原子
const themeAtom = atomWithStorage('theme', 'light')
// 异步原子
const userAtom = atom(async () => {
const res = await fetch('/api/user')
return res.json()
})
// 使用
function Counter() {
const [count, setCount] = useAtom(countAtom)
const doubled = useAtomValue(doubledAtom) // 只读
const setTheme = useSetAtom(themeAtom) // 只写
return (
<div>
<p>{count} × 2 = {doubled}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
)
}Redux Toolkit
ts
import { createSlice, createAsyncThunk, configureStore } from '@reduxjs/toolkit'
// createAsyncThunk:处理异步操作
const fetchUsers = createAsyncThunk('users/fetchAll', async () => {
const res = await fetch('/api/users')
return res.json()
})
// createSlice:简化 reducer + action 创建
const usersSlice = createSlice({
name: 'users',
initialState: {
list: [] as User[],
loading: false,
error: null as string | null
},
reducers: {
addUser: (state, action) => {
state.list.push(action.payload) // Immer 允许直接修改
},
removeUser: (state, action) => {
state.list = state.list.filter(u => u.id !== action.payload)
}
},
extraReducers: builder => {
builder
.addCase(fetchUsers.pending, state => { state.loading = true })
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false
state.list = action.payload
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false
state.error = action.error.message ?? '未知错误'
})
}
})
// 配置 Store
const store = configureStore({
reducer: {
users: usersSlice.reducer,
}
})
// 类型推断
type RootState = ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch
// 使用
import { useSelector, useDispatch } from 'react-redux'
function UserList() {
const users = useSelector((state: RootState) => state.users.list)
const dispatch = useDispatch<AppDispatch>()
useEffect(() => {
dispatch(fetchUsers())
}, [dispatch])
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}选型决策树
需要管理状态?
↓
只在当前组件用?
→ 是 → useState / useReducer
→ 否 ↓
是服务端数据(API)?
→ 是 → TanStack Query / SWR
→ 否 ↓
需要 URL 可分享?
→ 是 → URL 搜索参数
→ 否 ↓
Vue 项目?
→ 是 → Pinia
→ 否 ↓
需要细粒度原子更新?
→ 是 → Jotai
→ 否 ↓
团队规模大,需要严格规范?
→ 是 → Redux Toolkit
→ 否 → Zustand(推荐默认选择)总结
- 不要过度设计:大多数状态用 useState 就够了
- 服务端状态和客户端状态要分开管理
- Zustand 是 React 项目的最佳默认选择:简单、高性能、TypeScript 友好
- Redux Toolkit 适合大型团队,规范严格但样板代码多
- 状态尽量本地化,只有真正需要共享时才提升