如何使用Intersection Observer实现调用API的React无限滚动功能
问题排查与修正方案
现有代码的核心错误点如下:
- IntersectionObserver对应的useEffect没有设置依赖项、没有配置销毁逻辑,同时直接使用
setPage(page + 1)会触发闭包陷阱,拿到的始终是初始的page值,无法正确递增 - 所有列表项都绑定了同一个
lastItemref,导致ref始终指向遍历的最后一个元素,且没有在元素更新后重新绑定观察逻辑 - 更新图片列表时直接用新数据替换旧数据,不会保留之前加载的内容
- 没有加载状态锁,滚动触发时可能同时发起多个重复请求
以下是修正后的完整可运行代码:
import { useState, useRef, useEffect, useCallback } from 'react'; // 保留你原有导入:Nav、Footer、样式文件等 const YourComponent = () => { const [page, setPage] = useState(1); const [images, setImages] = useState([]); // 新增加载状态锁,防止重复触发请求 const [isLoading, setIsLoading] = useState(false); const observer = useRef(); // 封装最后一个列表元素的观察逻辑,用useCallback避免重复创建 const lastItemRef = useCallback(node => { if (isLoading) return; // 先断开之前的观察绑定 if (observer.current) observer.current.disconnect(); observer.current = new IntersectionObserver(entries => { // 仅当目标元素进入视口时才递增页码 if (entries[0].isIntersecting) { // 用函数式更新避免闭包陷阱 setPage(prevPage => prevPage + 1); } }); if (node) observer.current.observe(node); }, [isLoading]); // 数据请求逻辑 useEffect(() => { async function fetchData() { setIsLoading(true); const res = await fetch(`https://api.themoviedb.org/3/trending/movie/week?api_key=${apiKey}&page=${page}`); const data = await res.json(); // 追加新数据而非替换原有数据 setImages(prev => [...prev, ...data.results]); setIsLoading(false); } fetchData(); }, [apiKey, page]); if (!images.length && isLoading) { return <h2>Loading...</h2>; } return ( <> <Nav /> <main> <section className={styles.spacer}> <h1>Trending Movies</h1> <ul className={styles.grid}> {images.map((image, index) => { return ( <li className={styles.gridItem} key={image.id} // 仅给列表最后一个元素绑定观察ref ref={index === images.length - 1 ? lastItemRef : null} > <img src={`https://image.tmdb.org/t/p/w370_and_h556_bestv2/${image.poster_path}`} alt={image.original_title} /> <p className={styles.title}>{image.original_title}</p> </li> ); })} </ul> {isLoading && <p>加载更多内容中...</p>} </section> </main> <Footer /> </> ); }
内容的提问来源于stack exchange,提问作者Andrei Nistor
相关产品推荐
相关产品推荐

