7.1 错误处理是生产代码的分水岭
Demo 里"能拿到数据"就行,但生产环境要面对:网络抖动、超时、401 过期、500 服务器错误。TanStack Query 提供了从"自动重试"到"全局兜底"再到"错误边界"的完整链路。
7.2 核心概念
- retry
- 失败后自动重试次数,查询默认
3,mutation 默认0。可传数字、false、或(failureCount, error) => boolean函数做条件重试(如 401 不重试)。 - retryDelay
- 两次重试的间隔,默认指数退避(1s、2s、4s…上限 30s)。可自定义函数控制退避曲线。
- throwOnError
- 为 true 时把查询错误抛给最近的 React Error Boundary,而非用 isError 就地处理。可传函数按错误类型决定(如仅 5xx 抛给边界)。旧名 useErrorBoundary。
- Error Boundary (错误边界)
- React 捕获子树渲染错误的组件。配合 throwOnError,让查询错误走统一的错误 UI,而不必每个组件写 if (isError)。
- useQueryErrorResetBoundary
- 提供 reset 函数,让错误边界的"重试"按钮能重置查询错误状态并重新获取——把边界和 query 的错误联动起来。
- QueryCache onError
- 在 QueryClient 上配置的全局错误回调,任何查询失败都会触发。适合统一弹 toast、上报监控、处理 401 登出。
- useSuspenseQuery
- Suspense 版查询 Hook:加载时抛 Promise 给 <Suspense> 显示 fallback,出错抛给 Error Boundary。data 永不为 undefined,组件里无需再判空。
7.3 配置重试策略
useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
retry: (failureCount, error) => {
// 4xx 不重试(客户端错误重试也没用)
if (error instanceof HttpError && error.status < 500) return false;
return failureCount < 3;
},
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30_000),
});
7.4 就地错误处理(最常见)
function Todos() {
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
});
if (isLoading) return <Skeleton />;
if (isError) return (
<div role="alert">
<p>加载失败:{error.message}</p>
<button onClick={() => refetch()}>重试</button>
</div>
);
return <List items={data} />;
}
7.5 全局错误兜底
import { QueryClient, QueryCache, MutationCache } from "@tanstack/react-query";
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// 只在没有本地错误 UI 时才全局提示(可用 meta 标记)
if (query.meta?.errorMessage) {
toast.error(query.meta.errorMessage as string);
}
if ((error as HttpError).status === 401) redirectToLogin();
},
}),
mutationCache: new MutationCache({
onError: (err) => reportToSentry(err),
}),
});
用 meta 传递上下文
每个查询可配 meta: { errorMessage: "加载订单失败" },全局 onError 里读取它给出针对性提示,既集中又保留个性化——避免所有错误都弹一样的通用文案。
7.6 错误边界 + 重置
import { useQueryErrorResetBoundary } from "@tanstack/react-query";
import { ErrorBoundary } from "react-error-boundary";
function Page() {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
<p>出错了</p>
<button onClick={resetErrorBoundary}>重试</button>
</div>
)}
>
<Todos /> {/* 内部用 throwOnError 或 useSuspenseQuery */}
</ErrorBoundary>
);
}
7.7 Suspense 集成
import { useSuspenseQuery } from "@tanstack/react-query";
function Profile({ id }: { id: number }) {
// data 一定有值,无需 isLoading/undefined 判断
const { data } = useSuspenseQuery({
queryKey: ["user", id],
queryFn: () => fetchUser(id),
});
return <h1>{data.name}</h1>;
}
// 父组件用 Suspense + ErrorBoundary 统一处理 loading/error
<ErrorBoundary fallback={<Err />}>
<Suspense fallback={<Skeleton />}>
<Profile id={1} />
</Suspense>
</ErrorBoundary>
Suspense 的取舍
useSuspenseQuery 让组件代码超干净(无判空),但 loading/error 交给上层边界,粒度较粗;且它不支持 enabled、placeholderData 等选项。数据流复杂时仍建议用普通 useQuery 就地处理。
7.8 三种错误处理方式对比
| 方式 | 粒度 | 适用 |
|---|---|---|
| isError 就地处理 | 单组件 | 需要局部错误 UI、可重试 |
| throwOnError + 边界 | 子树 | 整块区域统一错误页 |
| QueryCache onError | 全局 | toast、监控上报、401 登出 |
7.9 小结与下一步
- retry + retryDelay 自动重试,用函数对 4xx 短路、5xx 退避。
- 就地 isError 最灵活;throwOnError + 错误边界做区域兜底。
- QueryCache/MutationCache 的 onError 做全局 toast、监控、登出。
- useSuspenseQuery 让 data 恒有值,交由 Suspense/边界处理加载与错误。
- 下一章进阶 高级模式——select、useQueries、queryOptions 复用与类型安全。