You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 17:54:00