React中Intersection Observer引发数组重复等问题求助
问题描述
基于Intersection Observer实现React无限滚动后遇到三个问题:
- 展开、折叠再重新展开由
hideSection控制的区块时,数组数据重复,引发大量React重复key错误; - 偶尔滚动时page值异常,单次触发页码增加2而非1;
- 不进行展开/折叠操作时,也偶尔出现重复key错误。
所有记录均有唯一ID,API分页数据经验证正确,调整useEffect和useInfiniteScroll后无效,求解决建议。
相关代码
父组件
export function Section({ combine }: SectionProps) { const [hideSection, setHideSection] = useState(false); const { ref, position } = useInfiniteScroll(); const { serverData, isLoading, totalCount } = useFetchData(position, hideSection); return ( <main> {!combine && <> <Group totalCount={totalCount} key="completed" title="Data" data={serverData} hidden={hideSection} setHidden={setHideSection} isLoading={isLoading} /> <div ref={ref}></div> </>} </main> ) }
数据获取Hook
export function useFetchData(page: number, isHidden: boolean) { const [serverData, setServerData] = useState<any>([]); const [totalCount, setTotalCount] = useState<number>(0); const [position, setPosition] = useState<number>(page); const { data, isLoading } = useApiGetData().usePaging( { pageNumber: page, pageSize: 20, }, !!page, { refetchOnWindowFocus: false } ); useEffect(() => { setPosition(page); if (!isHidden && position === page) { setServerData((prev: any[]) => prev.concat(data?.data)); } setTotalCount(data?.totalRecords || 0); }, [isHidden, data, position]); return { isLoading, serverData, totalCount }; }
Intersection Observer Hook
export function useInfiniteScroll() { const [position, setPosition] = useState(0); const ref = useRef(null); const handleObserver = useCallback((entries) => { entries.forEach((entry: any) => { if (entry.intersectionRatio > 0) { setPosition((prev) => prev + 1); } }); }, []); useEffect(() => { const option = { root: null, rootMargin: '0px', threshold: 1 }; const observer = new IntersectionObserver(handleObserver, option); if (ref.current) observer.observe(ref.current); }, [handleObserver]); return { ref, position }; }
解决方案
1. 解决展开折叠后数据重复问题
问题根源是hideSection切换时未重置数据,且Hook内部状态与传入参数不同步:
- 在
useFetchData中监听isHidden变化,隐藏时清空数据:useEffect(() => { if (isHidden) { setServerData([]); } }, [isHidden]); - 移除
useFetchData内部的position状态,直接使用传入的page参数,避免状态不同步;同时添加重复数据检查,防止重复拼接:export function useFetchData(page: number, isHidden: boolean) { const [serverData, setServerData] = useState<any>([]); const [totalCount, setTotalCount] = useState<number>(0); const { data, isLoading } = useApiGetData().usePaging( { pageNumber: page, pageSize: 20 }, !!page, { refetchOnWindowFocus: false } ); // 隐藏时重置数据 useEffect(() => { if (isHidden) setServerData([]); }, [isHidden]); // 分页数据拼接逻辑 useEffect(() => { if (!isHidden && data?.data?.length && page > 0) { // 检查是否已存在该页数据 const hasDuplicate = serverData.some(item => data.data.some(newItem => newItem.id === item.id) ); if (!hasDuplicate) { setServerData(prev => [...prev, ...data.data]); } } if (data?.totalRecords) setTotalCount(data.totalRecords); }, [page, data, isHidden, serverData]); return { isLoading, serverData, totalCount }; }
2. 解决page值异常增加的问题
因Intersection Observer可能多次触发回调,需添加加载状态判断,避免加载中重复触发页码增加:
- 修改
useInfiniteScroll,接收isLoading参数控制页码更新:export function useInfiniteScroll(isLoading: boolean) { const [position, setPosition] = useState(0); const ref = useRef(null); const handleObserver = useCallback((entries) => { entries.forEach((entry: any) => { // 仅在元素进入视口且非加载状态时更新页码 if (entry.isIntersecting && !isLoading) { setPosition(prev => prev + 1); } }); }, [isLoading]); useEffect(() => { const option = { root: null, rootMargin: '0px', threshold: 1 }; const observer = new IntersectionObserver(handleObserver, option); const currentRef = ref.current; if (currentRef) observer.observe(currentRef); // 清理函数避免内存泄漏 return () => { if (currentRef) observer.unobserve(currentRef); }; }, [handleObserver]); return { ref, position }; } - 父组件中传递
isLoading给Hook:const { ref, position } = useInfiniteScroll(isLoading);
3. 解决重复key错误
除了防止数据重复,必须确保列表渲染时使用唯一ID作为key:
- 在
Group组件内部渲染列表项时,严格使用每条数据的唯一ID(如item.id):// Group组件内列表渲染示例 {data.map(item => ( <ListItem key={item.id} {...item} /> ))}
内容的提问来源于stack exchange,提问作者cking888
相关产品推荐
相关产品推荐

