useQuery重渲染多次触发请求仅执行一次onSuccess回调问题
问题分析与解决方案
问题根源
你遇到的情况是因为React Query的默认行为:每当useQuery的queryKey发生变化时,它会自动取消之前未完成的请求——React Query认为新的查询参数才是当前需要的,旧请求已经过时。当你短时间内连续调用5次setlazyLoadRowDataQuery,每次都会更新queryKey里的参数,前4次请求还没完成就被取消,自然不会触发onSuccess回调,只有最后一次未被取消的请求会执行回调。
解决方案
方案一:优化触发逻辑,避免重复/频繁请求
先通过标记已加载的行,减少不必要的状态更新和请求,从根源上避免请求被取消的情况:
- 维护一个集合存储已加载的行ID
- IntersectionObserver触发时,先检查该行是否已加载,未加载时才发起请求
// 新增状态:存储已加载的行key const [loadedRowKeys, setLoadedRowKeys] = useState(new Set()); const [lazyLoadRowDataQuery, setlazyLoadRowDataQuery] = useState(null); // ... 在IntersectionObserver的回调中修改: if (e.isIntersecting) { const timerId = setTimeout(() => { const rowKeyNum = +rowKey; const element = paginatedData.find(item => item.key === rowKeyNum); // 仅当该行未加载过时才触发请求 if (element && !loadedRowKeys.has(rowKeyNum)) { setLoadedRowKeys(prev => new Set([...prev, rowKeyNum])); setlazyLoadRowDataQuery(element); } }, 1000); timeouts.current[rowKey] = timerId; }
方案二:使用独立查询实例(适配多请求场景)
如果需要每个行的请求都独立执行onSuccess回调,可以用useQueries创建多个独立的查询实例,每个请求的生命周期互不干扰:
// 维护需要加载的行列表 const [targetRows, setTargetRows] = useState([]); // 在IntersectionObserver回调中添加待加载行 if (e.isIntersecting) { const timerId = setTimeout(() => { const rowKeyNum = +rowKey; const element = paginatedData.find(item => item.key === rowKeyNum); // 避免重复添加 if (element && !targetRows.some(r => r.key === rowKeyNum)) { setTargetRows(prev => [...prev, element]); } }, 1000); timeouts.current[rowKey] = timerId; } // 使用useQueries创建多个独立查询 const rowDataQueries = useQueries( targetRows.map(row => ({ queryKey: [ `/api/call`, { ...filters, queries: getQueryMetrics(selectedDimensions, row), } ], queryFn: postWithoutCancellation, enabled: true, onSuccess: (data) => { console.log('行加载成功', row.key, data); // 这里处理该行的后续逻辑,比如更新表格数据 } })) );
方案三:关闭自动请求取消(不推荐)
如果必须保留频繁请求的场景,可以通过useQuery的配置关闭自动取消,但这会导致多个请求同时进行,可能占用额外资源:
const metricDataLazyLoad = useQuery( [ `/api/call`, { ...filters, queries: getQueryMetrics(selectedDimensions, lazyLoadRowDataQuery), }, ], postWithoutCancellation, { enabled: !!lazyLoadRowDataQuery, onSuccess: (data) => { console.log('On success callback', data, lazyLoadRowDataQuery) }, cancelRefetch: false, // 阻止React Query取消旧请求 keepPreviousData: true // 保留之前的查询数据,同时发起新请求 } );
推荐方案
优先选择方案一,它既解决了onSuccess不触发的问题,又能减少不必要的网络请求和组件重渲染,提升整体性能。如果业务确实需要每个行的请求都独立执行回调,再考虑方案二。
内容的提问来源于stack exchange,提问作者Ronak Radadiya
相关产品推荐
相关产品推荐

