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

IntersectionObserver含await时回调触发两次,分页加载异常求助

问题描述

我一次性获取了70条结果,想按每10条分块展示模拟无限滚动加载。但加了1秒延迟(await delay(1000))后,loadCount每次会涨30,控制台还会两次打印“is intersecting”;去掉延迟功能就正常,但没法模拟loading状态。这问题出在哪?

相关代码:

const delay = (ms: number) =>
  new Promise((res) => setTimeout(res, ms));

const handleObserver = useCallback(
  async (entries: IntersectionObserverEntry[]) => {
    const target = entries[0];
    if (target.isIntersecting) {
      console.log('is intersecting');
      setMockRefetchLoading(true);
      // await delay(1000);
      setLoadCount((prev) => prev + 10);
      setMockRefetchLoading(false);
    }
  },
  []
);

console.log('load count', loadCount);

useEffect(() => {
  const option = {
    root: null,
    rootMargin: '20px',
    threshold: 0,
  };
  const observer = new IntersectionObserver(handleObserver, option);
  if (moreResultsLoader.current)
    observer.observe(moreResultsLoader.current);
  return () => {
    if (moreResultsLoader.current)
      observer.unobserve(moreResultsLoader.current);
  };
}, [handleObserver]);
问题原因
  1. IntersectionObserver重复触发:加了1秒延迟后,加载占位符(moreResultsLoader)会在视口内停留整整1秒,IntersectionObserver会多次触发isIntersecting事件——不仅初始进入视口时触发,延迟期间只要元素没移出视口,就会反复触发回调。
  2. 异步回调的竞态叠加:因为handleObserver是异步函数,多次触发的回调会同时进入延迟等待状态。等1秒结束后,这些回调会依次执行setLoadCount(prev => prev + 10),最终导致loadCount被多次累加(比如触发3次就加30)。
  3. 缺少加载防重机制:当前代码既没有在加载期间暂停监听,也没有判断当前是否处于loading状态,只要元素在视口内,就会不断触发加载逻辑。
修复方案

方案1:加载期间暂停监听 + 状态锁

把Observer实例存到ref里,方便在回调中控制监听状态,同时通过loading状态防止重复触发:

const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
const observer = useRef<IntersectionObserver | null>(null);

const handleObserver = useCallback(
  async (entries: IntersectionObserverEntry[]) => {
    const target = entries[0];
    // 只有元素在视口且未处于加载状态时才执行
    if (target.isIntersecting && !mockRefetchLoading) {
      console.log('is intersecting');
      setMockRefetchLoading(true);
      // 暂停监听加载元素
      observer.current?.unobserve(moreResultsLoader.current!);
      
      await delay(1000);
      setLoadCount(prev => prev + 10);
      setMockRefetchLoading(false);
      
      // 加载完成后重新监听
      observer.current?.observe(moreResultsLoader.current!);
    }
  },
  [mockRefetchLoading]
);

useEffect(() => {
  const option = {
    root: null,
    rootMargin: '20px',
    threshold: 0,
  };
  observer.current = new IntersectionObserver(handleObserver, option);
  if (moreResultsLoader.current) {
    observer.current.observe(moreResultsLoader.current);
  }
  return () => {
    if (moreResultsLoader.current) {
      observer.current?.unobserve(moreResultsLoader.current);
    }
  };
}, [handleObserver]);

方案2:简化版——只加状态锁

如果不想操作监听状态,也可以直接通过mockRefetchLoading状态做判断,阻止同一时间多次触发加载:

const handleObserver = useCallback(
  async (entries: IntersectionObserverEntry[]) => {
    const target = entries[0];
    if (target.isIntersecting && !mockRefetchLoading) {
      console.log('is intersecting');
      setMockRefetchLoading(true);
      await delay(1000);
      setLoadCount(prev => prev + 10);
      setMockRefetchLoading(false);
    }
  },
  [mockRefetchLoading]
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 03:15:42