Next.js 13客户端组件状态不更新且卡顿问题求助
问题排查与解决方案
一、「Load more」按钮无法更新页码、重复请求问题
问题原因
核心是闭包陷阱:HandleLoadMore函数在组件渲染时被创建,捕获的是当时的CurrentPage状态值。如果异步请求未完成时重复点击按钮,函数会一直使用旧的CurrentPage值,导致请求参数重复,页码也无法正确更新。此外没有加载状态控制,会引发并发请求进一步加重问题。
解决方案
- 用
useRef跟踪最新页码,避免闭包捕获旧值 - 添加加载状态,防止重复点击
- 使用函数式更新
posts状态,确保基于最新数组拼接
修改后的MultiplePosts组件代码:
'use client'; import PostCard from "@/components/PostCard"; import { useState, useEffect, useRef, useCallback } from "react"; const MultiplePosts = ({ posts, session, slug }) => { const [Posts, setPosts] = useState(posts); const [CurrentPage, setCurrentPage] = useState(1); const [isLoading, setIsLoading] = useState(false); const currentPageRef = useRef(CurrentPage); useEffect(() => { currentPageRef.current = CurrentPage; }, [CurrentPage]); const HandleLoadMore = useCallback(async () => { if (isLoading) return; setIsLoading(true); const newPage = currentPageRef.current + 1; try { const newData = await GetMorePostsUserPage(session, slug, newPage); setPosts(prevPosts => [...prevPosts, ...newData]); setCurrentPage(newPage); } catch (err) { console.error('加载更多帖子失败:', err); } finally { setIsLoading(false); } }, [isLoading, session, slug]); return ( <div className="post-infinite-container"> {Posts.map((post) => ( <PostCard key={post.id} post={post} comments={post.comments} session={session} /> ))} <button data-next-page={CurrentPage} type="button" onClick={HandleLoadMore} disabled={isLoading} > {isLoading ? '加载中...' : 'Load More'} </button> </div> ) }
二、组件卡顿、无响应问题
问题原因
- PostCard的id属性写法错误:
"post-item-" + post.id在JSX中会被解析为两个表达式,导致DOM元素id异常,引发不必要的重渲染或DOM操作开销 - 无限制重渲染:
PostCard未做缓存优化,父组件更新时所有卡片都会重渲染;HandleLoadMore未用useCallback包裹,导致按钮每次都重渲染 - 冗余数据传递:API返回的帖子数据可能包含大量不必要字段,加重渲染负担
- session对象引用变化:服务器组件传递的整个session对象可能每次渲染都生成新引用,触发客户端组件重渲染
解决方案
- 修复PostCard的id属性,使用模板字符串拼接
- 用
React.memo缓存PostCard组件,避免不必要重渲染 - 精简session传递,只传客户端需要的字段
- 优化API返回数据,裁剪掉不必要的字段
修改后的PostCard组件:
import { memo } from "react"; const PostCard = ({ post, comments, session }) => { const isAuthor = post.author.slug === session.userName; return ( <div id={`post-item-${post.id}`} className="card p-0 mb-3"> <div className="post-header px-3 pt-3"> <PostHeader post={post} isAuthor={isAuthor}/> </div> <hr/> <div className="post-content px-3"> <PostContent post={post} isAuthor={isAuthor}/> </div> <hr/> <div className="post-footer px-3 pb-3"> <PostFooter post={post} comments={comments} session={session}/> </div> </div> ) } export default memo(PostCard);
修改服务器组件Page.jsx:
export default async function Page(props){ const {slug} = props.params; const {user, posts, subscriptions} = await getUserPageBySlug(slug); const session = await getServerSession(options); // 只传递需要的session字段 const sessionProps = session ? { userName: session.user.name } : null; return ( <> <UserCover user={user} /> <div className="container"> <div className="row align-items-start"> <div className="user-card-container col-12 col-lg-3 order-lg-1 order-1 sticky-lg-top sticky-user"> <UserCard user={user} session={session} /> </div> <div className="col-12 col-lg-6 order-lg-2 order-3"> <Filters /> <MultiplePosts posts={posts} session={sessionProps} slug={slug} /> </div> <div className="col-12 col-lg-3 order-lg-3 order-2"> <SubscriptionCard user={user} subscriptions={subscriptions} session={session}/> </div> </div> </div> </> ) }
内容的提问来源于stack exchange,提问作者jmur
相关产品推荐
相关产品推荐

