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

React中IntersectionObserver回调内state变量未更新的无限滚动实现问题

问题:IntersectionObserver无限滚动中loading状态的竞态问题

我正尝试用IntersectionObserver实现无限滚动功能,逻辑是当回调触发时,先判断是否正在加载,如果是就取消本次调用。现在的实现函数是这样的:

const loadMore = useCallback(() => { 
  if (loading) { 
    return 
  } 
  setLoading(true); 
  console.log('Loading'); 
  setTimeout(() => { 
    console.log('Finished'); 
    setLoading(false); 
  }, 5000); 
}, []);

但遇到了问题:当观察者再次调用loadMore时,如果已有加载操作在进行,loading状态有时还是旧值(false),导致重复触发setTimeout。要是把loading加入useCallback的依赖数组,又会因为函数内部修改loading值引发循环调用。

我试过用useRef控制加载状态,逻辑上能正常工作,但我还需要在JSX里用loading状态控制加载文本的显示/隐藏,而useRef变量没法在JSX中生效,同时维护两个几乎一样的变量又不够优雅。想问下有没有仅使用state就能解决这个问题的方案?

观察者相关代码如下:

useEffect(() => { 
  const options = { 
    root: null, 
    rootMargin: '10px', 
    threshold: 1.0 
  } 
  const observer = new IntersectionObserver(loadMore, options); 
  if (loaderRef && loaderRef.current) { 
    observer.observe(loaderRef.current); 
  } 
}, [loadMore]);

解决方案:只用State搞定竞态与循环调用问题

别担心,其实完全可以只用state解决这个问题,核心是调整状态更新的逻辑和依赖处理,避免闭包带来的旧值问题和循环调用,具体做法如下:

1. 改用函数式更新判断加载状态

把loadMore里的状态判断改成函数式更新,这样每次都能拿到最新的loading值,同时不用把loading加入useCallback的依赖数组,也就不会引发循环调用:

const loadMore = useCallback(() => {
  // 通过函数式更新获取最新的loading状态
  setLoading(prevLoading => {
    // 如果已经在加载,直接返回当前状态,不执行后续逻辑
    if (prevLoading) return prevLoading;

    // 开始加载流程
    console.log('Loading');
    setTimeout(() => {
      console.log('Finished');
      setLoading(false);
    }, 5000);
    
    // 返回新的状态值
    return true;
  });
}, []); // 这里不需要依赖loading,函数式更新帮我们拿到最新值

2. 完善Observer的销毁逻辑

另外,你的useEffect需要加上销毁观察者的清理函数,防止组件卸载后还触发回调,而且现在loadMore的依赖是空数组,useEffect只会执行一次,不会重复创建观察者:

useEffect(() => { 
  const options = { 
    root: null, 
    rootMargin: '10px', 
    threshold: 1.0 
  };
  const observer = new IntersectionObserver(loadMore, options); 
  const targetElement = loaderRef.current;

  if (targetElement) { 
    observer.observe(targetElement); 
  } 

  // 组件卸载或依赖变化时,停止观察目标元素
  return () => {
    if (targetElement) {
      observer.unobserve(targetElement);
    }
  };
}, [loadMore]);

原理说明

  • 函数式更新setLoading(prev => ...)会让React把最新的状态值传给我们,彻底避免了闭包捕获旧loading值的问题,再也不会出现“明明在加载却判断成false”的情况。
  • loadMore的依赖数组是空的,所以它只会在组件初始化时创建一次,useEffect也只会执行一次,不会引发循环调用。
  • loading作为state变量,完全可以直接在JSX中用来控制加载文本的显示/隐藏,不需要额外维护ref变量,代码更简洁优雅。

这样调整后,你的无限滚动逻辑就正常了,而且完全只用state实现,满足你的需求~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:04:09