使用react-intersection-observer实现无限滚动时fetchData重复调用问题求助
解决react-intersection-observer无限滚动重复调用fetchData的问题
问题描述
我正在开发一个无限滚动组件,使用react-intersection-observer的hook时遇到问题:当inView状态为true时,fetchData函数会被调用两次。问题流程如下:
- 页面加载时,
inView为false。 - 用户滚动后,
inView变为true。 - 异步调用
fetchData,useEffect执行结束。 - 接口返回结果后,
setQueryData更新状态。 - 状态更新触发useEffect再次执行,但此时新数据还没渲染,关联
inView的组件仍在可视范围内。 - 因为
inView仍为true,fetchData被再次调用。
我想过在useEffect执行后立刻把inView设为false,但不确定是否可行,求建议。
现有代码
type LoadMoreState = { data: QueryDatabaseResponse[]; nextCursor: string | undefined; }; export function LoadMore({ cursor }: { cursor: string }) { const { ref, inView } = useInView(); const [queryData, setQueryData] = useState<LoadMoreState>({ data: [], nextCursor: cursor, }); useEffect(() => { const fetchData = async () => { if (inView) { try { const res = await getGoodFirstIssues({ page_size: 10, start_cursor: queryData.nextCursor, }); setQueryData((prevData) => ({ data: prevData.data.concat(res), nextCursor: res.next_cursor || undefined, })); } catch (error) { console.log("There was an error fetching the data", error); } } }; fetchData(); }, [inView, queryData]);
解决方案
核心问题分析
你的useEffect依赖了整个queryData对象,每次setQueryData更新状态后,useEffect会重新执行;同时没有添加请求锁机制,导致inView为true时重复发起请求。
具体修复方案
1. 添加请求锁状态
新增isFetching状态标记当前是否正在请求,避免同一条件下重复调用接口:
type LoadMoreState = { data: QueryDatabaseResponse[]; nextCursor: string | undefined; }; export function LoadMore({ cursor }: { cursor: string }) { const { ref, inView } = useInView(); const [queryData, setQueryData] = useState<LoadMoreState>({ data: [], nextCursor: cursor, }); // 新增请求锁状态 const [isFetching, setIsFetching] = useState(false); useEffect(() => { const fetchData = async () => { // 只有在可视、未在请求、且还有下一页数据时才执行 if (inView && !isFetching && queryData.nextCursor) { setIsFetching(true); try { const res = await getGoodFirstIssues({ page_size: 10, start_cursor: queryData.nextCursor, }); setQueryData((prevData) => ({ data: prevData.data.concat(res), nextCursor: res.next_cursor || undefined, })); } catch (error) { console.log("There was an error fetching the data", error); } finally { // 请求结束后释放锁 setIsFetching(false); } } }; fetchData(); }, [inView, queryData, isFetching]); return ( <div ref={ref}> {/* 可选:添加加载提示 */} {isFetching ? <div>加载中...</div> : null} </div> ); }
2. 优化useEffect依赖(可选)
将queryData.nextCursor单独提取作为依赖,减少因queryData.data变化触发的不必要执行:
export function LoadMore({ cursor }: { cursor: string }) { // ... 其他状态保持不变 const nextCursor = queryData.nextCursor; useEffect(() => { const fetchData = async () => { if (inView && !isFetching && nextCursor) { setIsFetching(true); try { const res = await getGoodFirstIssues({ page_size: 10, start_cursor: nextCursor, }); setQueryData((prevData) => ({ data: prevData.data.concat(res), nextCursor: res.next_cursor || undefined, })); } catch (error) { console.log("There was an error fetching the data", error); } finally { setIsFetching(false); } } }; fetchData(); }, [inView, nextCursor, isFetching]); }
3. 调整useInView配置(可选)
通过配置threshold确保组件完全进入视口才触发,减少误触发概率:
const { ref, inView } = useInView({ threshold: 1.0, // 组件100%进入视口才触发 });
注意:不要直接修改inView状态
inView是react-intersection-observer内部维护的状态,无法手动修改,强行干预会破坏hook的正常逻辑,添加请求锁是更合理的解决方案。
内容的提问来源于stack exchange,提问作者WhisperingZebra
相关产品推荐
相关产品推荐

