何时使用
- 在 React 应用里搭建全局状态、需要跨多个组件共享数据。
- 管理服务端状态(远程数据的请求、缓存、失效、乐观更新)。
- 需要在 Redux Toolkit / Zustand / Jotai / React Query / SWR 间做选型决策。
- 排查状态相关问题,或把旧版 Redux 迁移到 Redux Toolkit。
- 触发词:状态管理、全局状态、Redux、Zustand、Jotai、React Query、服务端状态、乐观更新、状态选型。
不该用的边界:
- 任务与 React 状态无关,或是非 React 框架(Vue/Svelte/Angular)→ 不属于本技能。
- 单组件本地 UI 状态用
useState/useReducer即可解决,不要为它引入全局 store。 - 纯后端逻辑、数据建模、无界面的脚本 → 不属于本技能。
- 要做有强视觉诉求的界面实现 → 配合
frontend-design。
步骤 / 指令
第 1 步:先给状态分类,不要把所有东西都塞进全局 store。
| 类型 | 说明 | 选型 |
|---|---|---|
| 本地状态 | 组件私有、纯 UI | useState、useReducer |
| 全局状态 | 跨组件共享 | Redux Toolkit、Zustand、Jotai |
| 服务端状态 | 远程数据、缓存 | React Query、SWR、RTK Query |
| URL 状态 | 路由参数、查询串 | React Router、nuqs |
| 表单状态 | 输入值、校验 | React Hook Form、Formik |
第 2 步:按规模与场景选库(决策流程)。
小型应用、状态简单 → Zustand 或 Jotai
大型应用、状态复杂 → Redux Toolkit
重度服务端交互 → React Query + 轻量客户端状态
需要原子化/细粒度更新 → Jotai
第 3 步:客户端状态与服务端状态分治——客户端 UI 状态用 Zustand/Jotai,远程数据交给 React Query 托管,不要把服务端数据复制进客户端 store。
第 4 步:用 selector 做选择性订阅,避免无关组件重渲染;派生数据用计算而非另存一份。
第 5 步:服务端写操作配乐观更新:onMutate 先取消在途请求并快照旧值、立即写入新值;onError 回滚;onSettled 失效重拉。
示例
Zustand(最简,带 devtools + 持久化):
// store/useStore.ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface AppState {
user: User | null
theme: 'light' | 'dark'
setUser: (user: User | null) => void
toggleTheme: () => void
}
export const useStore = create<AppState>()(
devtools(persist(
(set) => ({
user: null,
theme: 'light',
setUser: (user) => set({ user }),
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
}),
{ name: 'app-storage' }
))
)
Redux Toolkit(typed hooks + createAsyncThunk):
// store/index.ts
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
// slices/userSlice.ts
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId: string, { rejectWithValue }) => {
const res = await fetch(`/api/users/${userId}`)
if (!res.ok) return rejectWithValue('Failed to fetch user')
return res.json()
}
)
// extraReducers 里挂 pending/fulfilled/rejected 三态
Jotai 原子状态(基础/派生/持久化/只写 action):
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
export const userAtom = atom<User | null>(null)
export const isAuthenticatedAtom = atom((get) => get(userAtom) !== null) // 派生
export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light')
export const logoutAtom = atom(null, (get, set) => { // 只写 action
set(userAtom, null)
localStorage.removeItem('token')
})
React Query 服务端状态(query keys 工厂 + 乐观更新):
export const userKeys = {
all: ['users'] as const,
detail: (id: string) => [...userKeys.all, 'detail', id] as const,
}
export function useUpdateUser() {
const qc = useQueryClient()
return useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
await qc.cancelQueries({ queryKey: userKeys.detail(newUser.id) })
const previous = qc.getQueryData(userKeys.detail(newUser.id))
qc.setQueryData(userKeys.detail(newUser.id), newUser) // 乐观写入
return { previous }
},
onError: (_e, newUser, ctx) =>
qc.setQueryData(userKeys.detail(newUser.id), ctx?.previous), // 回滚
onSettled: (_d, _e, v) =>
qc.invalidateQueries({ queryKey: userKeys.detail(v.id) }), // 失效重拉
})
}
旧版 Redux → RTK 迁移(手写 action/switch reducer 换成 createSlice,Immer 允许「直接改」):
const todosSlice = createSlice({
name: 'todos',
initialState: [] as Todo[],
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
state.push({ text: action.payload, completed: false }) // Immer 代理
},
},
})
注意事项
- 不要过度全局化:不是所有状态都该进全局 store,能就近放就别上浮。
- 不要复制服务端状态:远程数据交给 React Query 托管,别在客户端 store 里再存一份导致两边不同步。
- 不要直接 mutate:除 RTK/Immer 代理外,一律走不可变更新。
- 不要存派生数据:派生值用计算(selector / 派生 atom),存下来必然会过期。
- 不要混范式:每类状态选一个主方案,别在同一类里混用多套。
- 用 selector / 选择性订阅控制重渲染;全量类型化(TypeScript)以杜绝运行时错误。
- React Query 注意
staleTime与gcTime(旧名cacheTime)的区别;enabled: !!id避免无效请求。 - 本技能给的是范式与选型判断,落地仍需结合你的项目做环境验证与测试,不替代专家评审。
互见
- requires:无。
- related:
frontend-design(状态搭好后做有设计感的界面实现)、webapp-testing(在浏览器里验证状态驱动的交互行为)。 - combines_with:
frontend-design(状态层 + 视觉层组合成完整前端)、webapp-testing(「实现—验证」闭环)。
采编自 sickn33/antigravity-awesome-skills(MIT)。