React无限滚动功能初始数据重复显示问题求助
React无限滚动初始数据重复显示问题排查与解决
问题现象
- 页面加载完成后,初始数据集(1-9)被正确获取并展示;
- 初始加载阶段,该数据集(1-9)会被显示两次,直到滚动触发后续页面加载;
- 滚动过重复数据段后,后续页面(第10条及以后)的数据才能正常展示。
代码示例
import React, { useEffect, useState } from 'react'; import MovieComponent from './MovieComponent'; import Loading from './Loading'; const Home = () => { const [card, setCard] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); const getCardData = async () => { const res = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=9&_page=${page}` ); const data = await res.json(); console.log("data got is ", data); setCard((prev) => [...prev, ...data]); setLoading(false); }; useEffect(() => { getCardData(); }, [page]); const handleInfiniteScroll = () => { try { if ( document.documentElement.scrollTop + window.innerHeight + 1 >= document.documentElement.scrollHeight ) { setLoading(true); setPage((prev) => prev + 1); } } catch (error) { console.log(error); } }; useEffect(() => { window.addEventListener('scroll', handleInfiniteScroll); return () => window.removeEventListener('scroll', handleInfiniteScroll); }, []); return ( <> <MovieComponent movieInfo={card} /> {loading && <Loading />} </> ); }; export default Home;
问题核心原因
- React严格模式的重复执行:开发环境下,React严格模式会触发
useEffect执行两次,导致初始时两次调用getCardData(page=1),重复添加相同数据; - 初始页面高度不足触发滚动事件:如果初始加载的9条数据高度小于窗口高度,滚动事件的触发条件会直接满足,提前触发
page递增,但异步请求的时序问题可能导致重复请求同一页数据。
解决方案
方案1:阻止严格模式下的初始重复请求
通过useRef标记初始请求是否完成,避免严格模式下的重复调用:
import React, { useEffect, useState, useRef } from 'react'; // ...其他导入不变 const Home = () => { const [card, setCard] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); const isInitialFetchDone = useRef(false); // 标记初始请求完成状态 const getCardData = async () => { // 初始请求已完成则直接返回 if (page === 1 && isInitialFetchDone.current) return; const res = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=9&_page=${page}` ); const data = await res.json(); // 数据为空时停止监听,避免无效请求 if (data.length === 0) { setLoading(false); window.removeEventListener('scroll', handleInfiniteScroll); return; } setCard((prev) => [...prev, ...data]); setLoading(false); // 标记初始请求完成 if (page === 1) isInitialFetchDone.current = true; }; // ...其他代码不变 };
方案2:添加请求锁防止并发请求
新增状态标记是否正在请求,避免同一时间发起多个重复请求:
const Home = () => { const [card, setCard] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); const [isFetching, setIsFetching] = useState(false); // 请求锁 const getCardData = async () => { // 正在请求则直接返回 if (isFetching) return; setIsFetching(true); try { const res = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=9&_page=${page}` ); const data = await res.json(); if (data.length === 0) { window.removeEventListener('scroll', handleInfiniteScroll); return; } setCard((prev) => [...prev, ...data]); } catch (error) { console.log(error); } finally { setLoading(false); setIsFetching(false); // 释放请求锁 } }; // ...其他代码不变 };
方案3:延迟添加滚动监听
等初始数据渲染完成后再添加滚动监听,避免初始时因页面高度不足触发不必要的请求:
useEffect(() => { // 初始数据加载完成后再绑定滚动监听 if (card.length > 0) { window.addEventListener('scroll', handleInfiniteScroll); return () => window.removeEventListener('scroll', handleInfiniteScroll); } }, [card]); // 依赖card状态,数据更新后再执行
总结
优先组合方案1和方案2,既能解决React严格模式下的重复请求问题,又能防止并发请求导致的重复数据,同时优化滚动触发逻辑避免无效请求。
内容的提问来源于stack exchange,提问作者Prakriti
相关产品推荐
相关产品推荐

