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

React中IntersectionObserver回调state为undefined及无限循环问题

React无限滚动(Intersection Observer)解决方案:避免state undefined与无限循环

问题根源

  • 回调函数因闭包捕获旧值,导致未在依赖中加入data时,回调里的data始终是初始的undefined;
  • 若将data加入useEffect依赖,每次setData更新后都会重新创建Observer实例,触发重复监听与加载,进而引发无限循环。

解决方案1:使用函数式更新获取最新state

通过setData的函数式更新语法,无需在useEffect依赖中加入data,即可拿到最新状态,同时避免重复创建Observer。

import { useEffect, useRef, useState } from 'react';

const ThreeColumnGrid = () => {
  const [data, setData] = useState([]);
  const loaderRef = useRef(null);
  const observerRef = useRef(null);

  // 模拟接口请求替换成你的真实业务逻辑
  const loadMore = async () => {
    const newItems = await fetch(`/api/grid-data?offset=${data.length}`).then(res => res.json());
    // 函数式更新,确保拿到最新的state
    setData(prevData => [...prevData, ...newItems]);
  };

  useEffect(() => {
    // 仅在组件挂载时创建一次Observer
    observerRef.current = new IntersectionObserver(entries => {
      const [entry] = entries;
      if (entry.isIntersecting) {
        loadMore();
      }
    });

    // 监听底部加载指示器
    if (loaderRef.current) {
      observerRef.current.observe(loaderRef.current);
    }

    // 组件卸载时清理Observer,防止内存泄漏
    return () => {
      observerRef.current?.disconnect();
    };
  }, []); // 依赖为空,只执行一次

  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem', padding: '1rem' }}>
      {data.map(item => (
        <div key={item.id} style={{ padding: '1rem', border: '1px solid #eee' }}>
          {item.content}
        </div>
      ))}
      {/* 触发滚动加载的指示器 */}
      <div ref={loaderRef} style={{ gridColumn: '1 / -1', padding: '2rem', textAlign: 'center' }}>
        加载中...
      </div>
    </div>
  );
};

export default ThreeColumnGrid;

解决方案2:用useRef同步最新state引用

如果需要在回调中直接访问最新的data,可以用useRef同步state,避免闭包问题。

import { useEffect, useRef, useState } from 'react';

const ThreeColumnGrid = () => {
  const [data, setData] = useState([]);
  const dataRef = useRef(data);
  const loaderRef = useRef(null);
  const observerRef = useRef(null);

  // 同步state到ref,确保ref始终保存最新值
  useEffect(() => {
    dataRef.current = data;
  }, [data]);

  const loadMore = async () => {
    const newItems = await fetch(`/api/grid-data?offset=${dataRef.current.length}`).then(res => res.json());
    setData([...dataRef.current, ...newItems]);
  };

  useEffect(() => {
    observerRef.current = new IntersectionObserver(entries => {
      const [entry] = entries;
      if (entry.isIntersecting) {
        loadMore();
      }
    });

    if (loaderRef.current) {
      observerRef.current.observe(loaderRef.current);
    }

    return () => {
      observerRef.current?.disconnect();
    };
  }, []);

  // 渲染部分同方案1
};

额外优化建议

  • 添加加载状态:避免同一时间重复触发请求,比如在loadMore中判断isLoading状态;
  • 处理无更多数据:当接口返回空数组时,停止监听加载指示器,并提示用户“没有更多数据”。

示例优化后的loadMore与加载指示器:

const [isLoading, setIsLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);

const loadMore = async () => {
  if (isLoading || !hasMore) return;
  setIsLoading(true);
  try {
    const newItems = await fetch(`/api/grid-data?offset=${data.length}`).then(res => res.json());
    if (newItems.length === 0) {
      setHasMore(false);
    } else {
      setData(prev => [...prev, ...newItems]);
    }
  } catch (err) {
    console.error('加载失败:', err);
  } finally {
    setIsLoading(false);
  }
};

// 加载指示器渲染
<div ref={loaderRef} style={{ gridColumn: '1 / -1', padding: '2rem', textAlign: 'center' }}>
  {isLoading ? '加载中...' : hasMore ? '' : '没有更多数据了'}
</div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 02:48:25