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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:35:01