QueryClient 配置策略
QueryClient 的全局默认配置、Provider 放置位置、环境差异化配置,以及 queryClient 实例的生命周期管理。
#type / howto
#status / evergreen
#tech / dev / frontend
#resource / react
[!info] related notes
- 前置: TanStack Query 服务端状态
- 所属 MOC: TanStack Query 知识地图
- 关联: gcTime vs staleTime
- 关联: 错误处理与重试策略
- 关联: DevTools 调试工具
QueryClient 配置策略
基本配置
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000, // 1 分钟
gcTime: 5 * 60_000, // 5 分钟
retry: 2, // 重试 2 次
refetchOnWindowFocus: false, // 禁用窗口聚焦刷新
},
mutations: {
retry: 1,
},
},
});
defaultOptions.queries 完整配置
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 数据新鲜度:多久内不重新请求
staleTime: 0, // 默认 0,立即 stale
// 垃圾回收时间:无订阅者后多久清除缓存
gcTime: 5 * 60 * 1000, // 默认 5 分钟
// 重试次数
retry: 3, // 默认 3 次
// 重试延迟(指数退避)
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30_000),
// 窗口聚焦时是否重新获取
refetchOnWindowFocus: true, // 默认 true
// 组件挂载时是否重新获取
refetchOnMount: true, // 默认 true
// 网络重连时是否重新获取
refetchOnReconnect: true, // 默认 true
// 网络模式
networkMode: 'online', // 'online' | 'always' | 'offlineFirst'
// 是否在后台获取时抛出错误
throwOnError: false,
// 结构共享
structuralSharing: true, // 默认 true
},
},
});
环境差异化配置
// config/query-client.ts
export function createQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: import.meta.env.DEV ? 0 : 60_000,
gcTime: import.meta.env.DEV ? 30_000 : 5 * 60_000,
retry: import.meta.env.DEV ? 0 : 2,
refetchOnWindowFocus: import.meta.env.DEV ? false : true,
},
},
});
}
// 开发环境:staleTime 0 + 不重试 + 不自动刷新 → 方便调试
// 生产环境:合理的缓存 + 重试 + 自动刷新 → 用户体验
Provider 放置
最佳实践
// main.tsx 或 app.tsx
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = createQueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router>
<YourApp />
</Router>
{import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} />}
</QueryClientProvider>
);
}
注意事项
// ❌ 在组件内部创建 queryClient
function App() {
const queryClient = new QueryClient(); // 每次 render 都创建新实例!
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>;
}
// ✅ 在组件外部创建(全局单例)
const queryClient = new QueryClient();
function App() {
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>;
}
// ✅ 或用 useState/useRef 保证只创建一次
function App() {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: { queries: { staleTime: 60_000 } },
}));
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>;
}
全局错误处理
import { QueryCache, MutationCache } from '@tanstack/react-query';
const queryCache = new QueryCache({
onError: (error, query) => {
// 按 queryKey 分类处理
if (error.status === 401) {
authStore.logout();
return;
}
// 静默失败的 query 不提示
if (query.meta?.silent) return;
toast.error(error.message);
},
});
const mutationCache = new MutationCache({
onError: (error, variables, context, mutation) => {
if (mutation.meta?.silent) return;
toast.error(`操作失败: ${error.message}`);
},
onSuccess: (data, variables, context, mutation) => {
if (mutation.meta?.showSuccess !== false) {
toast.success('操作成功');
}
},
});
const queryClient = new QueryClient({
queryCache,
mutationCache,
});
使用 meta 标记静默查询
// 不需要 toast 的查询
useQuery({
queryKey: ['health'],
queryFn: checkHealth,
meta: { silent: true },
});
// 不需要成功提示的 mutation
useMutation({
mutationFn: updateDraft,
meta: { showSuccess: false },
});
全局事件监听
// 监听所有 query 状态变化
queryClient.getQueryCache().subscribe((event) => {
if (event.type === 'updated') {
const { query, action } = event;
console.log(`Query ${query.queryKey} updated:`, action.type);
}
});
// 监听网络状态
import { onlineManager } from '@tanstack/react-query';
onlineManager.subscribe(({ online }) => {
if (online) {
toast.success('网络已恢复');
} else {
toast.warning('网络已断开');
}
});
QueryClient 实例方法
const queryClient = new QueryClient();
// 预取
await queryClient.prefetchQuery({ queryKey: ['users'], queryFn: fetchUsers });
await queryClient.ensureQueryData({ queryKey: ['user', id], queryFn: () => fetchUser(id) });
// 缓存操作
queryClient.setQueryData(['user', id], newData);
queryClient.getQueryData(['user', id]);
queryClient.invalidateQueries({ queryKey: ['users'] });
queryClient.removeQueries({ queryKey: ['user', id] });
queryClient.cancelQueries({ queryKey: ['user', id] });
// 全局操作
queryClient.clear(); // 清除所有缓存
queryClient.resetQueries(); // 重置所有 query
queryClient.refetchQueries(); // 重新获取所有 query
// 获取 query 实例
const query = queryClient.getQueryState(['user', id]);
// { data, error, status, fetchStatus, dataUpdatedAt, ... }
常见错误
默认 staleTime 太短导致频繁请求
// ❌ 默认 staleTime: 0,每次挂载都请求
// 如果页面有 10 个组件都用同一个 query,会请求 10 次
// ✅ 设置合理的默认值
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 60_000 },
},
});
refetchOnWindowFocus 引起意外请求
// 用户切换 tab 再切回来,所有 stale 的 query 都会 refetch
// 如果有几十个 query,会产生大量请求
// ✅ 全局禁用或按需开启
const queryClient = new QueryClient({
defaultOptions: {
queries: { refetchOnWindowFocus: false },
},
});
// 特定 query 需要时单独开启
useQuery({
queryKey: ['notifications'],
queryFn: fetchNotifications,
refetchOnWindowFocus: true, // 通知需要实时
});
没有清理过期缓存
// ❌ gcTime 太长 + 动态 key → 缓存无限增长
useQuery({
queryKey: ['search', searchTerm], // 每次搜索都产生新 key
queryFn: () => search(searchTerm),
gcTime: 30 * 60_000, // 30 分钟
});
// 搜了 100 个词 → 100 个缓存条目
// ✅ 缩短 gcTime 或手动清理
useQuery({
queryKey: ['search', searchTerm],
queryFn: () => search(searchTerm),
gcTime: 60_000, // 1 分钟后清除
});