React JS对接Reddit实现无限滚动异常加载问题修复
问题根因
你的代码存在5个核心问题,才会导致无限加载/滚动无响应:
- 触发逻辑死循环:把数据请求放在依赖
page的useEffect中,请求完成后立刻更新page值,会直接触发下一次请求,完全不需要滚动操作,造成无限加载 - 滚动触发逻辑无效:
scrollToEnd方法中写setPage(page)是无意义操作,状态值没有变化不会触发更新,滚到底自然不会加载 - 无加载锁:没有判断请求状态,滚动事件多次触发时会重复发起并发请求
- 滚动判断兼容性差:用
===严格判断是否滚动到最底部,浏览器缩放、滚动精度、滚动条宽度差异都会导致条件永远不满足 - 事件绑定不规范:直接重写
window.onscroll,每次组件重渲染都会重复绑定事件,组件卸载时也没有清理,会造成内存泄漏;同时更新列表时直接取外层items变量,容易触发React闭包陷阱导致数据覆盖。
修正后完整代码
import React, {useState, useEffect, useCallback} from "react"; import logo from './logo.svg'; import './App.css'; import './Table.css'; import axios from "axios"; function App() { const [items, setItems] = useState([]); // 存下一页请求需要的after参数,初始为空字符串对应第一页 const [nextPage, setNextPage] = useState(""); // 加载锁,防止重复请求 const [loading, setLoading] = useState(false); // 标记是否还有更多数据 const [hasMore, setHasMore] = useState(true); // 抽离数据加载逻辑 const fetchMore = useCallback(() => { // 正在加载、或没有更多数据时直接返回 if (loading || !hasMore) return; setLoading(true); axios.get(`https://www.reddit.com/r/aww.json?after=${nextPage}`) .then(res=> res.data) .then(data => { // 函数式更新,避免闭包拿到旧的items值 setItems(prev => [...prev, ...data.data.children]); const newAfter = data.data.after; setNextPage(newAfter); // 返回的after为null代表已经到最后一页 if (!newAfter) setHasMore(false); }) .catch(err => console.error("加载失败:", err)) .finally(() => setLoading(false)); }, [loading, hasMore, nextPage]); // 组件挂载时自动加载第一页 useEffect(() => { fetchMore(); }, []); // 绑定滚动监听 useEffect(() => { const handleScroll = () => { // 距离底部100px时就触发加载,兼容性更好,体验更流畅 const reachBottom = window.innerHeight + document.documentElement.scrollTop >= document.documentElement.offsetHeight - 100; if (reachBottom) fetchMore(); }; window.addEventListener('scroll', handleScroll); // 组件卸载时移除事件监听,防止内存泄漏 return () => window.removeEventListener('scroll', handleScroll); }, [fetchMore]); return ( <div className="App"> <h1>Displaying data from Reddit API</h1> <table> <thead> <tr> <th>ID</th> <th>Photo</th> <th>Content</th> </tr> </thead> <tbody> {items.map((el) => <tr key={el.data.id} id={el.data.id}> <td>{el.data.id}</td> <td> <img src={el.data.thumbnail} height={el.data.thumbnail_height} width={el.data.thumbnail_width} alt={el.data.title} /> </td> <td>{el.data.title}</td> </tr> )} {loading && ( <tr> <td colSpan={3} style={{textAlign: 'center', padding: '1rem'}}> 加载中... </td> </tr> )} {!hasMore && !loading && ( <tr> <td colSpan={3} style={{textAlign: 'center', padding: '1rem'}}> 已经到底啦 </td> </tr> )} </tbody> </table> </div> ); } export default App;
关键实现规范
- 加载锁机制:通过
loading状态标记请求状态,请求发起前上锁、请求结束后解锁,从根源避免重复并发请求 - 状态更新规范:列表拼接使用函数式setState,始终基于最新的前序状态更新,避免React闭包陷阱导致的数据丢失/重复
- 事件绑定规范:滚动事件在useEffect中绑定,通过return的清理函数在组件卸载时移除,避免内存泄漏;不要直接重写
window.onscroll属性,用addEventListener绑定更安全 - 边界兼容处理:
- 滚动触发留100px提前量,不用严格相等判断,兼容不同设备、浏览器的滚动精度差异
- 新增
hasMore状态,接口返回无下一页标识时停止触发请求,减少无效请求 - 列表渲染补充
key属性、图片补充alt属性,符合React开发规范,消除控制台警告
- 逻辑解耦:把加载逻辑从依赖page的useEffect中抽离,首次加载、滚动加载都复用同一个
fetchMore方法,逻辑更清晰,不会出现状态变化自动触发请求的死循环问题。
内容的提问来源于stack exchange,提问作者Indication
相关产品推荐
相关产品推荐

