React无限滚动加载时如何从指定ID起始获取API数据避免重复
问题核心原因
你当前出现重复数据的问题本质是分页逻辑错误,和API是否支持自定义起始点无关:
- 首屏请求路径为
/friends/1/20,对应接口规则是第1页、每页20条,成功拉取前20条数据后,你没有更新分页状态,初始page值一直停留在1 - 滚动触发加载时,你传入的页码还是1,且单页条数改成了40,相当于重复请求第1页的40条数据,自然会包含首屏已经加载的前20条
- 同时你直接取闭包内的
items做数组合并,在滚动连续触发时很容易拿到旧状态,进一步加剧重复、数据丢失问题
关于你提到的「能否自行选择数据起始点,仅拉取ID大于等于指定值的数据」,核心取决于后端API的能力:如果API开放了min_id/since_id/offset这类过滤参数,传参即可实现服务端过滤,性能最优;如果API未开放相关能力,前端无法要求服务端只返回指定范围数据,只能拉取结果后在前端做过滤、去重。
可落地方案
方案1:修正分页逻辑(适配你当前使用的接口,无冗余请求)
你当前使用的接口是标准的页码分页接口,路径规则为/user/{用户id}/friends/{页码}/{每页条数},不需要额外传ID过滤参数,只要对齐分页规则即可彻底解决重复问题:
- 统一每页请求的条数,避免首屏拉20条、滚动加载拉40条的参数不一致问题
- 首屏加载完第1页数据后,把分页状态更新为2,下一次请求直接从第2页开始拉取
- 用React函数式状态更新规避闭包陷阱,同时加请求锁避免连续触发重复请求
修正后的核心代码如下:
const params = useParams(); const [items, setItems] = useState([]); // 统一每页加载条数 const PAGE_SIZE = 20; const [page, setPage] = useState(1); const [isFetching, setIsFetching] = useState(false); // 首屏加载第一页好友数据 const fetchInitialData = () => { setIsFetching(true); const url = `http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}/friends/1/${PAGE_SIZE}`; axios.get(url).then((res) => { setItems(res.data.list); // 第一页加载完成,页码更新为2,下一次直接拉第二页 setPage(2); setIsFetching(false); }); }; // 组件挂载时触发首屏加载 useEffect(() => { fetchInitialData(); }, [params.id]); // 滚动触发加载更多 const loadMoreData = () => { // 请求进行中直接返回,避免重复触发 if (isFetching) return; setIsFetching(true); const url = `http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}/friends/${page}/${PAGE_SIZE}`; axios.get(url).then(res => { // 函数式更新,避免闭包拿到旧的items值 setItems(prevItems => [...prevItems,...res.data.list]); // 页码自增 setPage(prevPage => prevPage + 1); setIsFetching(false); }); }
改完后首屏加载1-20条,第一次滚动加载21-40条,第二次滚动加载41-60条,完全不会出现重复数据。
方案2:前端去重兜底(适配不支持过滤、分页参数不规范的接口)
如果后续遇到不支持标准分页、也没有ID过滤能力的接口,可以在前端做唯一键去重兜底,哪怕接口返回重复数据也不会重复渲染:
axios.get(url).then(res => { setItems(prevItems => { // 提取已经加载过的所有好友唯一ID const existedIds = new Set(prevItems.map(item => item.id)); // 过滤掉已经存在的重复条目 const uniqueNewItems = res.data.list.filter(item =>!existedIds.has(item.id)); return [...prevItems,...uniqueNewItems]; }); setPage(prevPage => prevPage + 1); setIsFetching(false); })
这种方案的缺点是会拉取到已经加载过的冗余数据,浪费请求流量,仅作为兜底方案使用。
内容的提问来源于stack exchange,提问作者ceaiius
相关产品推荐
相关产品推荐

