Skip to content

状态管理全景

前端状态管理是架构设计的核心。选错工具会让代码复杂度爆炸,选对工具让开发如鱼得水。

状态分类

前端状态的四种类型:

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低频更新的全局状态内置,但性能有限
ZustandReact 全局状态极简 API,高性能
Jotai原子化状态细粒度更新
Redux Toolkit复杂企业应用规范严格,DevTools 强大
TanStack Query服务端状态缓存、同步、后台更新
PiniaVue 全局状态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 适合大型团队,规范严格但样板代码多
  • 状态尽量本地化,只有真正需要共享时才提升

系统学习 Web 前端生态,深入底层架构