You need to enable JavaScript to run this app. React实现无限滚动时window.addEventListener更新state仅能到2
优惠活动
大模型
产品
解决方案
定价
更多

React实现无限滚动时window.addEventListener更新state仅能到2

无限滚动实现的核心问题根因
  • 空依赖useEffect的闭包陷阱:注册在依赖为[]的effect内部的scrollHandler,只捕获了组件首次渲染时的scrollNumber初始值1。这个监听器从组件挂载后就不会随组件重渲染更新内部引用,哪怕后续scrollNumber实际值已经变成2,监听器逻辑里拿到的还是闭包存储的旧值,每次执行setScrollNumber(scrollNumber +1)本质都是基于旧值1计算,永远只能得到2,无法继续累加。
  • 逻辑冗余:两个独立的useEffect都在请求同一个分页接口,首次挂载时会触发两次完全重复的请求,没有统一的数据流入口。
  • 滚动触发条件过严:硬判断元素底部位置 === 窗口可视区高度,滚动过程中因为像素取整、滚动动画插值,几乎很难精准匹配完全相等的临界值,实际触发成功率极低。
  • 数组截取存在副作用:用result.splice()截取分页数据会直接修改接口返回的原数组本身,多次请求时因为原数组被篡改,取到的分页数据长度会不符合预期。
  • 缺少防重锁:滚动触发更新时,没有拦截短时间内的重复触发,容易导致分页参数被多次累加、重复发起请求。
对应修正逻辑
  • 滚动监听器内部的分页更新改用setScrollNumber(prev => prev +1)的函数式写法,不依赖闭包捕获的旧值,从根上解决闭包捕获过期state的问题。
  • 删除首次挂载时冗余的请求effect,只保留由scrollNumber驱动的数据加载逻辑,避免重复请求。
  • 调整滚动触发阈值,在距离可视区底部100px时就提前触发加载,兼顾滚动流畅度和触发成功率。
  • 数组截取改用无副作用的slice方法,保证分页数据截取的准确性。
  • 新增isLoading状态锁,请求过程中不再触发新的分页更新,避免重复拉取。
修正后可直接运行的完整代码
import React, { useEffect, useRef, useState } from 'react';

const Container: React.FC<{}> = () => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [scrollNumber, setScrollNumber] = useState<number>(1);
  const [posts, setPosts] = useState<any[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  // 分页数据加载逻辑:仅跟随scrollNumber变化执行
  useEffect(() => {
    const loadData = async () => {
      setIsLoading(true);
      try {
        const req = await fetch('https://jsonplaceholder.typicode.com/posts');
        const result = await req.json();
        // 用无副作用的slice截取对应页数的数据
        const pst = result.slice(0, 10 * scrollNumber);
        setPosts(pst);
      } finally {
        setIsLoading(false);
      }
    };
    loadData();
  }, [scrollNumber]);

  // 滚动监听器逻辑:组件挂载时绑定,卸载时解绑
  useEffect(() => {
    const scrollHandler = () => {
      // 请求中直接拦截,避免重复触发
      if (!containerRef.current || isLoading) return;
      const rect = containerRef.current.getBoundingClientRect();
      // 距离底部100px即触发下一页加载
      if (rect.bottom <= window.innerHeight + 100) {
        // 函数式更新,不依赖闭包捕获的旧state值
        setScrollNumber(prev => prev + 1);
      }
    };
    window.addEventListener('scroll', scrollHandler);
    return () => window.removeEventListener('scroll', scrollHandler);
  }, [isLoading]);

  return (
    <div ref={containerRef}>
      {posts.map(post => (
        <div key={post.id}>
          <br />
          <h1>{post.title}</h1>
          <p>{post.body}</p>
          <code>{post.userId}</code>
          <br /><br />
          <hr />
        </div>
      ))}
      {isLoading && <div style={{ textAlign: 'center', padding: '24px' }}>内容加载中...</div>}
    </div>
  );
};

export default Container;

内容的提问来源于stack exchange,提问作者Ek4m

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.13 18:45:08