TanStack Query 服务端状态管理

TanStack Query 的核心用法:useQuery 缓存 API 数据、useMutation 处理写操作、queryKey 设计、乐观更新、与 Zustand 的分工。

#type / howto #status / growing #tech / dev / frontend #resource / react

[!info] related notes

TanStack Query 服务端状态管理

核心问题

从 API 获取的数据有独特的生命周期问题:数据可能过期、可能被其他客户端修改、需要缓存避免重复请求、需要 loading/error 状态。用 useEffect + useState 手动管理这些问题会导致大量样板代码和 bug。

核心概念

queryKey — 缓存键

useQuery({ queryKey: ['consultations'], queryFn: fetchList });
useQuery({ queryKey: ['consultation', id], queryFn: () => fetchOne(id) });
  • 相同 key 共享缓存
  • key 变化时自动重新获取
  • key 要包含所有影响结果的参数

staleTime — 数据新鲜度

useQuery({
  queryKey: ['profile'],
  queryFn: fetchProfile,
  staleTime: 5 * 60 * 1000,  // 5 分钟内不重新请求
});
  • 0(默认):每次组件挂载都重新请求
  • Infinity:永远不自动重新请求(手动 invalidate)
  • 中间值:窗口期内用缓存,过期后后台刷新

useMutation — 写操作

const mutation = useMutation({
  mutationFn: (message: string) => sendMessage(sessionId, message),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['consultation', sessionId] });
  },
});

写操作后使相关缓存失效,触发重新获取。

queryKey 设计原则

// ✅ 包含所有影响结果的参数
useQuery({ queryKey: ['users', { page, limit, role }], queryFn: ... });

// ❌ 参数变化但 key 没变,不会重新获取
useQuery({ queryKey: ['users'], queryFn: () => fetchUsers(page, limit) });

乐观更新

先更新 UI,等服务器确认(或回滚):

const mutation = useMutation({
  mutationFn: updateProfile,
  onMutate: async (newData) => {
    await queryClient.cancelQueries({ queryKey: ['profile'] });
    const previous = queryClient.getQueryData(['profile']);
    queryClient.setQueryData(['profile'], newData);  // 立即更新
    return { previous };
  },
  onError: (err, newData, context) => {
    queryClient.setQueryData(['profile'], context.previous);  // 回滚
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['profile'] });  // 最终同步
  },
});

与 Zustand 的分工

Zustand: 认证 token、UI 状态、表单草稿
TanStack Query: 会话列表、评估报告、训练计划、用户档案

简单判断:数据来自 API → TanStack Query;数据在客户端产生 → Zustand

常见错误

useEffect + useState 手动 fetch

// ❌ 手动管理
const [data, setData] = useState(null);
useEffect(() => {
  fetch('/api/data').then(r => r.json()).then(setData);
}, []);

// ✅ 用 useQuery
const { data } = useQuery({ queryKey: ['data'], queryFn: () => fetch('/api/data').then(r => r.json()) });

不处理错误

// ❌ 忽略错误
const { data } = useQuery({ ... });

// ✅ 处理错误
const { data, error, isLoading } = useQuery({ ... });
if (error) return <ErrorMessage error={error} />;
创建于 2026/6/25 更新于 2026/6/25