React setState未保留历史状态:无限滚动组件items数组重置为空
无限滚动组件items重置为空数组的排查与修复
问题分析
你的无限滚动组件中,items数组在加载新数据时丢失旧数据(表现为重置为空),核心原因是闭包捕获旧状态和useEffect依赖项设置错误:
fetchData函数中直接使用的items是组件初始渲染时的空数组,由于useEffect依赖未正确设置,fetchData始终是初始版本,每次调用都会用空数组合并新数据,导致旧数据被覆盖。useEffect将observerTarget作为依赖,但useRef返回的ref对象本身是稳定的,不会触发useEffect重新执行,使得fetchData永远捕获初始的空items状态。
修复步骤
1. 改用函数式更新状态
将setItems改为函数式调用,直接获取最新的items状态,彻底避免闭包问题:
// 原代码 setItems([...items, ...data.data]); // 修改后 setItems(prevItems => [...prevItems, ...data.data]);
2. 用useCallback包裹fetchData并修正依赖
用useCallback包裹fetchData,确保当request变化时生成新的函数版本,同时将fetchData和loading加入useEffect依赖,保证回调能获取最新状态:
const fetchData = useCallback(async () => { setLoading(true); try { const res = await fetch(request.url, request.options); if (res.ok) { const data = await res.json(); setItems(prevItems => [...prevItems, ...data.data]); setError(null); // 请求成功时清空错误状态 } else { setError(new Error(`请求失败,状态码:${res.status}`)); } } catch (err) { console.log(err); setError(err); } finally { setLoading(false); } }, [request]);
3. 防止重复请求
在IntersectionObserver回调中加入!loading判断,避免数据加载过程中重复触发请求:
if (entries[0].isIntersecting && !loading) { console.log("isIntersecting, fetching new data..."); fetchData(); }
完整修复代码
"use client"; import { useState, useEffect, useRef, useCallback } from "react"; export function HBSInfiniteScrollList({ request = {} }) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const observerTarget = useRef(null); const fetchData = useCallback(async () => { setLoading(true); try { const res = await fetch(request.url, request.options); if (res.ok) { const data = await res.json(); setItems(prevItems => [...prevItems, ...data.data]); setError(null); } else { setError(new Error(`请求失败,状态码:${res.status}`)); } } catch (err) { console.log(err); setError(err); } finally { setLoading(false); } }, [request]); useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !loading) { console.log("isIntersecting, fetching new data..."); fetchData(); } }, { threshold: 1 } ); const target = observerTarget.current; if (target) { observer.observe(target); } return () => { if (target) { observer.unobserve(target); } }; }, [fetchData, loading]); return ( <div> <ul className="list-group"> {items.map((item) => ( <li key={item.id} className="list-group-item"> {item.name} </li> ))} </ul> {loading && <p>Loading...</p>} {error && <p>Error: {error.message}</p>} <p>length: {items.length}</p> <div ref={observerTarget}></div> </div> ); }
内容的提问来源于stack exchange,提问作者rsilva
相关产品推荐
相关产品推荐

