状态管理
选正确状态策略——服务端状态 vs 客户端状态——不过度工程化。
为什么
最常见 React 错误是把服务端数据放 useState/useReducer,意味着无缓存、无重取、无乐观更新、到处过期数据。服务端状态(API 数据)和客户端状态(UI 开关、表单输入)有完全不同生命周期应用不同工具。服务端状态属 TanStack Query(缓存、重取、失效);客户端状态属 useState 或跨组件共享用 Zustand。2026 年 Redux 几乎永不正确——它为 React Query 和服务端组件之前的世界设计。正确选择意味着更少代码、更少 bug、无'为何显示昨天数据?'工单。
何时使用
应用有 ≥3 数据获取组件时。搭 TanStack Query 约 1-2 小时。静态内容站可跳过。
如何做
1. 装 TanStack Query 并在 QueryClientProvider 包应用
- const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } })
- 放根布局,路由之上
2. 所有服务端状态(GET 请求)用 useQuery
- const { data, isLoading, error } = useQuery({ queryKey: ["users"], queryFn: fetchUsers })
- key 是缓存身份;同 key = 同数据
3. 写(POST/PUT/DELETE)用 useMutation 带缓存失效
- 成功:queryClient.invalidateQueries({ queryKey: ["users"] })
- 乐观更新:onMutate 在服务端响应前更新缓存
4. 本地 UI 状态(模态开、选标签)用 useState
- 仅 ≥3 兄弟组件需同客户端状态时用 Zustand
- 永不把服务端数据放 Zustand——属查询缓存
5. 依赖注入(主题、locale)用 React Context,非状态
- 每状态变更都重渲染的 Context 致性能问题示例
// 服务端状态——TanStack Query
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
export function UserList() {
const { data: users, isLoading } = useQuery({ queryKey: ["users"], queryFn: fetchUsers });
const qc = useQueryClient();
const mutation = useMutation({
mutationFn: deleteUser,
onSuccess: () => qc.invalidateQueries({ queryKey: ["users"] }),
});
if (isLoading) return <Spinner />;
return users.map(u => <UserRow key={u.id} user={u} onDelete={() => mutation.mutate(u.id)} />);
}
// 客户端状态——Zustand(仅跨组件 UI 状态)
import { create } from "zustand";
export const useUIStore = create<{ sidebarOpen: boolean; toggle: () => void }>((set) => ({
sidebarOpen: false, toggle: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));常见问题
- ⚠服务端数据存 useState 手动重取,丢缓存、去重和后台刷新。
- ⚠一切用 Redux 因'我们熟悉',为开关加 200 行样板。
- ⚠服务端状态放 Zustand,两组件显示同数据不同版本。
- ⚠Context 持频繁变状态,每更新致每消费者重渲染。
最佳实践
- ✓默认 staleTime 30-60 秒,重复导航不重取——用户感知即时页面加载。
- ✓查询 key 用带变量数组(['user', userId])每实体独立缓存。
- ✓hover/链接聚焦时预取下页数据,导航感即时。
- ✓Zustand store 小而专注——每功能一 store,非全局 app store。