React项目中使用useRef实现无限滚动失败的问题排查
React 无限滚动失效问题排查与修复
核心问题分析
- Ref 绑定错误:你把
tableWrapperRef绑定在了外层overflow-x-auto的容器上,但实际产生垂直滚动的是内层带有max-h-[400px] overflow-y-auto的div,导致滚动事件监听的对象根本不是滚动容器。 - 数据覆盖而非追加:API请求成功后直接用新数据替换旧数据,没有实现无限滚动的“加载更多”逻辑。
- 滚动判断精度问题:严格判断
wrapper.scrollHeight - wrapper.scrollTop === wrapper.clientHeight容易因为浏览器像素计算的浮点数误差失效。 - 无重复请求控制:滚动到底部时会多次触发
setPage,导致重复调用API。
修复方案与代码调整
1. 修正 Ref 绑定目标
把tableWrapperRef绑定到真正的滚动容器上:
// 移除外层div的ref,绑定到内层滚动容器 <div className="overflow-x-auto -mx-6"> <div className="max-h-[400px] overflow-y-auto custom-scrollbar" ref={tableWrapperRef}> {/* 表格内容 */} </div> </div>
2. 调整滚动判断逻辑
添加加载状态与无更多数据标记,避免重复请求,同时优化滚动到底部的判断精度:
// 先声明必要状态(未声明的话添加) const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const handleScroll = useCallback(() => { const wrapper = tableWrapperRef.current; if (!wrapper || loading || !hasMore) return; // 增加1px容错,避免像素精度问题导致判断失败 const isScrolledToBottom = wrapper.scrollHeight - wrapper.scrollTop <= wrapper.clientHeight + 1; if (isScrolledToBottom) { setPage(prev => prev + 1); } }, [loading, hasMore]); useEffect(() => { const wrapper = tableWrapperRef.current; if (wrapper) { wrapper.addEventListener("scroll", handleScroll); } return () => { if (wrapper) { wrapper.removeEventListener("scroll", handleScroll); } }; }, [handleScroll]);
3. 修正API数据处理逻辑
将新数据追加到现有数据中,而非直接替换,同时标记是否还有更多数据:
useEffect(() => { // 首次加载或搜索时重置数据 if (page === 1) { setUserTransactions([]); setHasMore(true); } setLoading(true); const fetchData = async () => { try { let res; if (debouncedSearchQuery.trim() === "") { res = await axios.get( `${import.meta.env.VITE_APP_API_BASE_URL}/api/transaction/account-transactions/${userInformation._id}/${page}/15`, { headers: apiHeaders } ); } else { res = await axios.get( `${import.meta.env.VITE_APP_API_BASE_URL}/api/transaction/search-account-transactions/${userInformation?._id}/${page}/20/${debouncedSearchQuery}`, { headers: apiHeaders } ); } const decrypted = decryptData(res.data.data); // 追加新数据到现有列表 setUserTransactions(prev => [...prev, ...decrypted.transactionList]); // 返回数据条数小于每页数量时,标记无更多数据 setHasMore(decrypted.transactionList.length >= (debouncedSearchQuery ? 20 : 15)); } catch (err) { const decrypted = decryptData(err.response.data.data); if (decrypted?.status === false) { if (page === 1) setUserTransactions([]); setHasMore(false); } } finally { setLoading(false); } }; fetchData(); }, [page, debouncedSearchQuery, userInformation]);
4. 完善页面状态提示
在表格底部补充无更多数据的提示:
{loading && ( <div className="w-full flex justify-center items-center min-h-[10vh]"> <h6>Loading...</h6> </div> )} {!hasMore && userTransactions.length > 0 && ( <div className="w-full flex justify-center items-center min-h-[10vh]"> <h6>没有更多交易记录了</h6> </div> )} {userTransactions?.length === 0 && !loading && ( <div className="w-full flex justify-center items-center min-h-[10vh]"> <h6>No transactions found</h6> </div> )}
内容的提问来源于stack exchange,提问作者Steven
相关产品推荐
相关产品推荐

