React无限滚动Feed组件重复渲染问题及无需新Hook的解决方案
React无限滚动组件整体重渲染问题解决
问题描述
我是React新手,正在实现无限滚动功能,功能能正常运行,但每次加载新内容时,Feed组件都会因pageNum状态更新而整体重渲染。我使用了Intersection Observer,当滚动到页面底部时,通过自定义hook usePosts调用接口加载6条新数据。
自定义hook代码
const usePosts = (pageNum = 1) => { const [results, setResults] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const [error, setError] = useState({}); const [hasNextPage, setHasNextPage] = useState(false); const { category } = useArticlesContext(); useEffect(() => { setIsLoading(true); setIsError(false); setError({}); const controller = new AbortController(); const { signal } = controller; getPostsPageArticles(category, pageNum, { signal }) .then((data) => { console.log(data); setResults((prev) => [...prev, ...data]); setHasNextPage(Boolean(data.length)); setIsLoading(false); }) .catch((e) => { setIsLoading(false); if (signal.aborted) return; setIsError(true); setError({ message: e.message }); }); return () => controller.abort(); }, [pageNum, category]); useEffect(() => { setResults([]); }, [category]); return { isLoading, isError, error, results, hasNextPage }; };
Feed组件代码
const Feed = () => { const [pageNum, setPageNum] = useState(1); const { isLoading, isError, error, results, hasNextPage } = usePosts(pageNum); const intObserver = useRef(); const lastPostRef = useCallback( (post) => { if (isLoading) return; if (intObserver.current) intObserver.current.disconnect(); intObserver.current = new IntersectionObserver((posts) => { if (posts[0].isIntersecting && hasNextPage) { console.log('We are near the last post'); setPageNum((prev) => prev + 1); } }); if (post) intObserver.current.observe(post); }, [isLoading, hasNextPage], ); if (isLoading) return <div>Loading...</div>; if (isError) return <div>Error: {error.message}</div>; const content = results.map((post, i) => { if (results.length === i + 1) { return <ArticleCard ref={lastPostRef} article={post} />; } return <ArticleCard article={post} />; }); return ( <div className="feed-container"> {content} {isLoading && <p>Loading more posts...</p>} </div> ); };
ArticleCard组件代码
const ArticleCard = React.forwardRef(({ article }, ref) => { const articleCardBody = ( <> <div className="article-card__title"> <h2>{article.title}</h2> </div> <img src={article.img} alt="" className="article-card__img" /> <div className="article-card__content"> <p>{article.content}</p> </div> </> ); const content = ref ? ( <div className="article-card" ref={ref}> {articleCardBody} </div> ) : ( <div className="article-card">{articleCardBody}</div> ); return content; });
解决方案
重渲染并非核心问题,问题出在Feed中文章的展示方式。正确做法是将文章列表处理逻辑封装为单独组件,并将文章加载逻辑放入独立Context中。
文章列表组件代码
const ArticleList = React.forwardRef(({ article }, ref) => { // 补充日期格式化逻辑(原代码未完整给出) const articleDate = new Date(article.date).toLocaleString(); const cardItems = ( <> <div className="article-card__title"> <h2>{article.title}</h2> </div> <img src={`data:image/jpeg;base64,${article.img}`} alt="" className="article-card__img" /> <div className="article-card__category">{article.category}</div> <div className="article-card__content"> <p>{article.content}</p> </div> <div className="article-card__date">{articleDate}</div> </> ); const content = ref ? ( <div className="article-card" ref={ref}> {cardItems} </div> ) : ( <div className="article-card">{cardItems}</div> ); return content; });
更新后的Feed组件代码
const Feed = () => { const { fetchNextPage, hasNextPage, isFetchingNextPage, data, status, error, } = useArticlesContext(); const intObserver = useRef(); const lastPostRef = useCallback( (post) => { if (isFetchingNextPage) return; if (intObserver.current) intObserver.current.disconnect(); intObserver.current = new IntersectionObserver((posts) => { if (posts[0].isIntersecting && hasNextPage) { console.log('We are near the last post'); fetchNextPage(); } }); if (post) intObserver.current.observe(post); }, [isFetchingNextPage, fetchNextPage, hasNextPage], ); if (status === 'error') return <div>Error: {error.message}</div>; const content = data?.pages.map((pg) => { return pg.map((post, i) => { if (pg.length === i + 1) { return <ArticleList ref={lastPostRef} article={post} key={post.url} />; } return <ArticleList article={post} key={post.url} />; }); }); return ( <div className="feed-container"> {content} {isFetchingNextPage && <p>Loading more posts...</p>} </div> ); };
另外,将文章加载逻辑迁移到了独立的Context中,统一管理分页数据和加载状态,避免Feed组件因内部状态更新导致的不必要重渲染。
内容的提问来源于stack exchange,提问作者stickynotesrule
相关产品推荐
相关产品推荐

