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]);
问题原因
- IntersectionObserver重复触发:加了1秒延迟后,加载占位符(
moreResultsLoader)会在视口内停留整整1秒,IntersectionObserver会多次触发isIntersecting事件——不仅初始进入视口时触发,延迟期间只要元素没移出视口,就会反复触发回调。 - 异步回调的竞态叠加:因为
handleObserver是异步函数,多次触发的回调会同时进入延迟等待状态。等1秒结束后,这些回调会依次执行setLoadCount(prev => prev + 10),最终导致loadCount被多次累加(比如触发3次就加30)。 - 缺少加载防重机制:当前代码既没有在加载期间暂停监听,也没有判断当前是否处于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
相关产品推荐
相关产品推荐

