React自定义懒加载Hook触发无限循环问题求助
解决自定义useLazyFetch Hook的无限循环问题
核心问题分析
调用fetchApi()后,Redux状态更新触发UI重新渲染,导致带loadMoreRef的跟踪元素回到视口顶部,再次触发Intersection Observer的回调,进而重复调用fetchApi(),形成循环。以下是针对性的解决方法:
1. 增加请求状态锁,阻止重复触发
在useLazyFetch Hook中维护加载状态和是否还有更多数据的标记,只有当未在加载且还有数据可加载时,才允许触发请求:
import { useState, useEffect, useRef } from 'react'; function useLazyFetch(fetchApi, hasMore) { const [isLoading, setIsLoading] = useState(false); const observerRef = useRef(null); const fetchData = async () => { if (isLoading || !hasMore) return; setIsLoading(true); try { await fetchApi(); } catch (err) { // 处理请求错误 } finally { setIsLoading(false); } }; useEffect(() => { const observer = new IntersectionObserver( (entries) => { const entry = entries[0]; if (entry.isIntersecting && !isLoading && hasMore) { console.log('INTERSECTING...'); fetchData(); } }, { rootMargin: '0px 0px 100px 0px' } // 可根据需求调整触发提前量 ); if (observerRef.current) { observer.observe(observerRef.current); } return () => { if (observerRef.current) { observer.unobserve(observerRef.current); } }; }, [isLoading, hasMore, fetchData]); return { observerRef, isLoading }; }
- 关键:通过
isLoading阻止并发请求,hasMore需在数据加载完毕后设为false(比如返回的帖子数量小于每页条数时)。
2. 确保跟踪元素稳定,避免重复挂载
把带loadMoreRef的跟踪div放在列表外部(比如列表容器的最底部),避免因列表项更新导致元素被重新创建或位置异常:
function Home() { const { posts, hasMore } = useSelector(state => state.posts); const dispatch = useDispatch(); const { observerRef, isLoading } = useLazyFetch( () => dispatch(fetchPosts()), // fetchPosts为Redux切片的异步action hasMore ); return ( <div className="post-list-container"> {posts.map(post => ( <PostItem key={post.id} post={post} /> ))} {/* 跟踪元素独立于列表项,放在列表末尾 */} {hasMore && ( <div ref={observerRef} className="load-more-trigger" style={{ minHeight: '40px' }}> {isLoading ? '加载中...' : '滚动加载更多'} </div> )} </div> ); }
- 注意:给跟踪元素设置固定最小高度,避免因内容变化导致位置跳变。
3. 稳定fetchApi的引用,避免Hook频繁重执行
如果fetchApi是在组件内定义的函数,用useCallback包裹它,确保引用稳定,防止useLazyFetch内的useEffect频繁重新执行:
function Home() { // ... const fetchPosts = useCallback(() => { return dispatch(getPosts()); // getPosts为Redux异步action }, [dispatch]); const { observerRef, isLoading } = useLazyFetch(fetchPosts, hasMore); // ... }
4. 保留滚动位置,防止UI重渲染后滚动重置
如果Redux状态更新导致组件重新渲染时滚动位置丢失,用useRef记录滚动容器的滚动位置,在渲染后恢复:
function Home() { const containerRef = useRef(null); const scrollTopRef = useRef(0); // 保存滚动位置 useEffect(() => { const container = containerRef.current; const handleScroll = () => { scrollTopRef.current = container.scrollTop; }; container?.addEventListener('scroll', handleScroll); return () => container?.removeEventListener('scroll', handleScroll); }, []); // 帖子更新后恢复滚动位置 useEffect(() => { containerRef.current?.scrollTo(0, scrollTopRef.current); }, [posts]); return ( <div ref={containerRef} className="post-list-container"> {/* 列表和跟踪元素 */} </div> ); }
内容的提问来源于stack exchange,提问作者Saurabh
相关产品推荐
相关产品推荐

