React Hooks中setOffset无法更新状态,无限滚动重复获取数据
React无限滚动offset不更新问题修复
问题原因
这是React闭包陷阱导致的:你的useEffect依赖是空数组,里面绑定的handleScroll和fetchData都是第一次渲染时创建的,它们永远捕获初始的offset值(0)。哪怕调用setOffset更新了值,这些旧函数里的offset还是0,所以每次请求用的都是同一个offset,拿到重复数据。
修复方案
方案1:用useRef保存最新offset(推荐,性能更优)
useRef的.current属性会始终同步最新的state值,不受闭包捕获旧值的影响。
- 新增
offsetRef跟踪最新offset:
const [offset, setOffset] = useState(0); const offsetRef = useRef(offset);
- 同步offset到ref:
useEffect(() => { offsetRef.current = offset; }, [offset]);
- 修改
createURL使用offsetRef.current:
const createURL = () => { const url = new URL(API_BASE_URL); url.searchParams.set('q', 'hello'); url.searchParams.set('api_key', API_KEY); url.searchParams.set('offset', offsetRef.current); return url; }
方案2:通过useCallback更新函数依赖
让fetchData和handleScroll随offset变化重新创建,确保捕获最新的offset值:
- 用
useCallback包裹fetchData:
const fetchData = useCallback(async () => { try { const response = await fetch(createURL()); const { data, pagination } = await response.json(); setImages(prevImages => [...prevImages, ...data]); setOffset(prevOffset => prevOffset + 1); } catch(error) { console.error(error); } }, [offset]);
- 用
useCallback包裹handleScroll:
const handleScroll = useCallback(() => { if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight) { return; } fetchData(); }, [fetchData]);
- 更新
useEffect的依赖数组:
useEffect(() => { fetchData(); window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [fetchData, handleScroll]);
完整修复代码(方案1)
import { useEffect, useState, useRef } from 'react'; function App() { const API_KEY = '***'; const API_BASE_URL = 'https://api.giphy.com/v1/gifs/search'; const [images, setImages] = useState([]); const [offset, setOffset] = useState(0); const offsetRef = useRef(offset); // 同步offset到ref useEffect(() => { offsetRef.current = offset; }, [offset]); const handleScroll = () => { if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight) { return; } fetchData(); } const createURL = () => { const url = new URL(API_BASE_URL); url.searchParams.set('q', 'hello'); url.searchParams.set('api_key', API_KEY); url.searchParams.set('offset', offsetRef.current); return url; } const fetchData = async () => { try { const response = await fetch(createURL()); const { data, pagination } = await response.json(); setImages(prevImages => [...prevImages, ...data]); setOffset(prevOffset => prevOffset + 1); } catch(error) { console.error(error); } } useEffect(() => { fetchData(); window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []) return ( <div className="container"> <h1>Testimonials</h1> <div className="testimonialContainer"> {images.map((image,idx) => { return ( <p key={idx} className="testimonial">{image.title}</p> ) })} </div> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者user3620090
相关产品推荐
相关产品推荐

