React实现无限滚动时数据重复、重复请求及依赖警告问题排查
问题成因分析
1. 新增数据重复、无法加载新页数据的成因
- 闭包陷阱导致
page变量永远是初始值1:你定义的getData函数、scroll事件回调都是在组件首次渲染时创建的,捕获了当时作用域里的page=1,后续即使调用setPage修改了状态,这两个函数里拿到的page永远是初始的1,所以每次请求的都是第一页数据,自然会重复。 setPage是异步更新,你在判断条件里执行setPage(2)之后立刻调用getData,此时新的page状态还没更新,getData拿到的还是旧的page值。- 分页逻辑只写了切换到第2页的逻辑,没有设计自动累加页码的逻辑,就算没有闭包问题,也只能加载到第二页就停止。
- 更新
APIData时直接使用当前作用域的APIData拼接,受闭包影响拿到的永远是首次渲染的空数组,也会导致旧数据被覆盖、新数据重复拼接。
2. 滚动到底部后函数持续重复运行的成因
- scroll事件绑定位置错误:你直接在组件函数的顶层绑定
window.addEventListener,组件每重渲染一次就会新增一个scroll监听事件,多个监听会同时触发回调。 - 回调里的
page受闭包影响永远等于1,所以page === 1的判断条件永远成立,只要滚动到底部就会触发逻辑,不会因为你后续修改page状态而停止。 - 没有做请求防抖/节流,也没有加请求锁,同一时间可以发起多个重复请求,滚动事件触发频率极高,就会出现反复调用的情况。
- 没有在组件卸载时解绑scroll事件,就算组件销毁了监听还会持续运行,甚至引发内存泄漏。
3. useEffect依赖警告的成因
- 你在useEffect内部调用了
getData函数,但是依赖数组只传入了category,ESLint的React Hooks规则检测到缺失依赖就会抛出警告。而且getData函数每次组件重渲染都会重新创建,如果直接把它加入依赖数组会导致useEffect无限触发。
修复方案参考
import { useState, useEffect, useCallback } from 'react' export default function News({ category }) { const [APIData, setAPIData] = useState([]) const [page, setPage] = useState(1) const [isLoading, setIsLoading] = useState(false) // 新增请求锁,防止重复请求 const pageSize = 20 // 用useCallback包裹getData,稳定函数引用,避免每次渲染重新创建 const getData = useCallback(async () => { if (isLoading) return // 正在请求就跳过,防止重复拉取 setIsLoading(true) try { let response = await fetch(`https://newsapi.org/v2/top-headlines?country=in&category=${category}&pageSize=${pageSize}&page=${page}&apiKey={APIKey}`) let parsedResponse = await response.json() // 用函数式更新setAPIData,避免依赖旧的APIData状态 setAPIData(prev => prev.concat(parsedResponse.articles)) } catch (err) { console.error('请求失败', err) } finally { setIsLoading(false) } }, [category, page, pageSize, isLoading]) // 监听getData变化,自动拉取对应页数据 useEffect(() => { getData() }, [getData]) // 把scroll事件绑定放到useEffect里,组件卸载时解绑,避免重复绑定和内存泄漏 useEffect(() => { const handleScroll = () => { // 用宽松的相等判断,避免不同浏览器滚动高度计算偏差导致不触发,加上请求锁判断 if (window.innerHeight + document.documentElement.scrollTop >= document.scrollingElement.scrollHeight - 10 && !isLoading) { setPage(prev => prev + 1) // 页码自动累加,不要写死成2 } } window.addEventListener('scroll', handleScroll) // 组件卸载时解绑事件 return () => window.removeEventListener('scroll', handleScroll) }, [isLoading]) // 切换分类时重置页码和数据,避免不同分类内容混排 useEffect(() => { setAPIData([]) setPage(1) }, [category]) }
内容的提问来源于stack exchange,提问作者Yash Sharma
相关产品推荐
相关产品推荐

