最后更新:2026-03-12
React 状态管理这个话题能吵架,Redux 党、MobX 党、Context 党、Zustand 派,各有各的道理。
我自己用过几种方案,不同项目里都有实际经验。说说我现在的看法,不是推销某一种,是根据场景给建议。
先说说什么时候需要「状态管理库」
很多人一开始就上状态管理库,其实不必要。
先问自己:这个状态需要跨多少层组件共享?
- 只在当前组件用 →
useState
- 父子组件之间传 → props 就够了
- 三四层以内的子树 → Context + useReducer
- 跨越多个不相关的组件树,或者状态很复杂 → 状态管理库
很多中小型项目用 Context 就够了,强行上 Redux 是自找麻烦。
Context + useReducer:内置方案
对于中等复杂度的状态,不想引入第三方库时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { createContext, useContext, useReducer } from 'react'
type Theme = 'light' | 'dark' type Action = { type: 'TOGGLE' }
const ThemeContext = createContext<{ theme: Theme dispatch: React.Dispatch<Action> } | null>(null)
function themeReducer(state: Theme, action: Action): Theme { switch (action.type) { case 'TOGGLE': return state === 'light' ? 'dark' : 'light' default: return state } }
export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, dispatch] = useReducer(themeReducer, 'light')
return ( <ThemeContext.Provider value={{ theme, dispatch }}> {children} </ThemeContext.Provider> ) }
export function useTheme() { const context = useContext(ThemeContext) if (!context) throw new Error('useTheme 必须在 ThemeProvider 内使用') return context }
|
使用:
1 2 3 4 5 6 7 8 9
| function ThemeToggle() { const { theme, dispatch } = useTheme()
return ( <button onClick={() => dispatch({ type: 'TOGGLE' })}> 当前主题:{theme} </button> ) }
|
Context 的问题:任何消费这个 Context 的组件,只要 Context 值变了就会重新渲染,哪怕这个组件只用了其中一个字段。状态复杂、消费者多的时候会有性能问题。
Zustand:简单项目首选
Zustand 是我目前新项目的默认选择,没有之一。理由:
- API 极简,5 分钟上手
- 没有 Provider 包裹
- 按需订阅,不订阅的部分不会触发重渲染
- TypeScript 支持好
基本用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { create } from 'zustand'
interface User { id: number name: string }
interface UserStore { user: User | null isLoading: boolean login: (name: string) => Promise<void> logout: () => void }
export const useUserStore = create<UserStore>((set) => ({ user: null, isLoading: false,
login: async (name) => { set({ isLoading: true }) await new Promise((resolve) => setTimeout(resolve, 1000)) set({ user: { id: 1, name }, isLoading: false }) },
logout: () => { set({ user: null }) }, }))
|
组件里用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function Header() { const user = useUserStore((state) => state.user) const logout = useUserStore((state) => state.logout)
if (!user) return null
return ( <header> <span>你好,{user.name}</span> <button onClick={logout}>退出</button> </header> ) }
function LoginButton() { const isLoading = useUserStore((state) => state.isLoading) const login = useUserStore((state) => state.login)
return ( <button onClick={() => login('Eric')} disabled={isLoading}> {isLoading ? '登录中...' : '登录'} </button> ) }
|
注意这里的选择器写法 (state) => state.user,这样组件只在 user 变化时重渲染,isLoading 变化不影响 Header。
异步操作和错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| export const usePostStore = create<PostStore>((set, get) => ({ posts: [], loading: false, error: null,
fetchPosts: async () => { set({ loading: true, error: null }) try { const res = await fetch('/api/posts') const posts = await res.json() set({ posts, loading: false }) } catch (err) { set({ error: '加载失败,请重试', loading: false }) } },
deletePost: async (id: number) => { const prevPosts = get().posts set({ posts: prevPosts.filter((p) => p.id !== id) })
try { await fetch(`/api/posts/${id}`, { method: 'DELETE' }) } catch { set({ posts: prevPosts }) } }, }))
|
持久化存储
用 Zustand 内置的 persist 中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { create } from 'zustand' import { persist } from 'zustand/middleware'
export const useSettingsStore = create( persist( (set) => ({ language: 'zh-CN', theme: 'light', setLanguage: (lang: string) => set({ language: lang }), setTheme: (theme: string) => set({ theme }), }), { name: 'app-settings', } ) )
|
这样设置会自动同步到 localStorage,刷新页面不丢失。
如果你的项目很大,团队多人协作,数据流需要非常严格的规范,Redux Toolkit(RTK)是更好的选择。
RTK 是 Redux 官方维护的增强版,解决了原版 Redux 配置繁琐、模板代码太多的问题。
1
| npm install @reduxjs/toolkit react-redux
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const fetchPosts = createAsyncThunk('posts/fetchAll', async () => { const res = await fetch('/api/posts') return res.json() })
const postsSlice = createSlice({ name: 'posts', initialState: { items: [] as Post[], status: 'idle' as 'idle' | 'loading' | 'succeeded' | 'failed', }, reducers: { addPost(state, action) { state.items.push(action.payload) }, }, extraReducers: (builder) => { builder .addCase(fetchPosts.pending, (state) => { state.status = 'loading' }) .addCase(fetchPosts.fulfilled, (state, action) => { state.status = 'succeeded' state.items = action.payload }) .addCase(fetchPosts.rejected, (state) => { state.status = 'failed' }) }, })
export const { addPost } = postsSlice.actions export default postsSlice.reducer
|
RTK 的优点是规范统一,适合大团队。缺点是模板代码多,学习成本比 Zustand 高。
我现在的选型标准
| 场景 |
推荐方案 |
| 状态只在当前组件用 |
useState |
| 少量全局状态(主题、用户信息) |
Context 或 Zustand |
| 中等复杂度,团队 1-5 人 |
Zustand |
| 大型应用,5 人以上团队,需要严格数据流 |
Redux Toolkit |
| 服务端状态(接口数据缓存) |
TanStack Query(推荐配合) |
值得一提的是 TanStack Query(之前叫 React Query),它专门处理服务端状态——接口数据的缓存、loading 状态、失效重新请求等。跟 Zustand 配合用,前者管服务端数据,后者管客户端 UI 状态,分工很清晰。
别纠结选哪个,大部分项目 Zustand 都够用,上手快,代码干净,真的遇到瓶颈了再迁移也来得及。