使用usePrefetchQuery预取数据时如何处理404及离线错误并兼容useSuspenseQuery
问题描述
使用usePrefetchInfiniteQuery预取无限列表数据时,遇到URL错误导致404、后端离线这类场景,应用会直接崩溃并抛出控制台错误。但根据文档描述:The prefetch functions never throws errors because they usually try to fetch again in a useQuery which is a nice graceful fallback.,预期预取出错时应该自动跳过,由后续useQuery优雅降级。希望在保留现有预取逻辑的前提下,解决应用崩溃问题,实现预期的降级处理。
现有代码实现
const initialPageParam = "api/v1/logs-list/json/?limit=25&offset=0"; const queryFn = async (pageParam: string, customKy: KyInstance) => { const link = pageParam.split("a1pi/v1/")[1]; const resolvedLink = `${link}&resolved=false`; return await customKy.get<LogsResponse>(resolvedLink).json(); }; export const usePrefetchInfiniteLogsQuery = () => { const { customKy } = useKy(); usePrefetchInfiniteQuery({ queryKey: ["infiniteLogs", customKy], queryFn: async ({ pageParam }) => queryFn(pageParam, customKy), initialPageParam, getNextPageParam: (lastPage) => lastPage?.next, pages: 3, }); }; export const useInfiniteLogsQuery = () => { const { customKy } = useKy(); return useSuspenseInfiniteQuery({ queryKey: ["infiniteLogs", customKy], queryFn: async ({ pageParam }) => queryFn(pageParam, customKy), initialPageParam, getNextPageParam: (lastPage) => lastPage?.next, }); };
父组件使用方式
const SosFormWrapper = () => { usePrefetchSosQuery(); return ( <Suspense fallback={<Loader />}> <EmergencyModal /> <SosForm /> </Suspense> ); };
解决方案
1. 在queryFn内部主动捕获错误
预取崩溃的核心原因是queryFn抛出了未捕获的异常。可以在queryFn中直接捕获错误,返回带错误标记的结构而非抛出错误:
const queryFn = async (pageParam: string, customKy: KyInstance) => { try { const link = pageParam.split("a1pi/v1/")[1]; const resolvedLink = `${link}&resolved=false`; return await customKy.get<LogsResponse>(resolvedLink).json(); } catch (error) { // 捕获错误并返回带标记的结构,避免抛出异常 console.warn("预取日志数据失败:", error); return { data: [], next: null, isError: true } as Partial<LogsResponse>; } };
这样预取时即使出错,也不会导致应用崩溃,后续useSuspenseInfiniteQuery拿到数据后,可在组件中判断isError状态并做降级渲染。
2. 显式配置throwOnError: false
虽然预取函数默认不应抛出错误,但显式配置该参数可以强制抑制错误抛出:
export const usePrefetchInfiniteLogsQuery = () => { const { customKy } = useKy(); usePrefetchInfiniteQuery({ queryKey: ["infiniteLogs", customKy], queryFn: async ({ pageParam }) => queryFn(pageParam, customKy), initialPageParam, getNextPageParam: (lastPage) => lastPage?.next, pages: 3, throwOnError: false, // 强制关闭预取时的错误抛出 }); };
3. 在useSuspenseInfiniteQuery中处理降级逻辑
结合预取返回的错误标记,在查询钩子中封装错误判断逻辑,方便组件使用:
export const useInfiniteLogsQuery = () => { const { customKy } = useKy(); const result = useSuspenseInfiniteQuery({ queryKey: ["infiniteLogs", customKy], queryFn: async ({ pageParam }) => queryFn(pageParam, customKy), initialPageParam, getNextPageParam: (lastPage) => lastPage?.next, }); // 统一判断是否存在预取或查询错误 const hasError = result.pages.some(page => page?.isError); return { ...result, hasError }; };
组件中使用时:
const SosForm = () => { const { data, hasError } = useInfiniteLogsQuery(); if (hasError) { return <div>加载失败,请稍后重试</div>; } // 正常渲染列表逻辑 };
4. 全局错误边界兜底
添加React错误边界,捕获组件树中未处理的错误,避免整个应用崩溃:
class LogsErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error("日志模块错误:", error, errorInfo); } render() { if (this.state.hasError) { return <div>加载出现异常,请刷新页面重试</div>; } return this.props.children; } }
然后包裹目标组件:
const SosFormWrapper = () => { usePrefetchSosQuery(); return ( <Suspense fallback={<Loader />}> <LogsErrorBoundary> <EmergencyModal /> <SosForm /> </LogsErrorBoundary> </Suspense> ); };
内容的提问来源于stack exchange,提问作者magrega
相关产品推荐
相关产品推荐

