You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13客户端组件状态不更新且卡顿问题求助

问题排查与解决方案

一、「Load more」按钮无法更新页码、重复请求问题

问题原因

核心是闭包陷阱:HandleLoadMore函数在组件渲染时被创建,捕获的是当时的CurrentPage状态值。如果异步请求未完成时重复点击按钮,函数会一直使用旧的CurrentPage值,导致请求参数重复,页码也无法正确更新。此外没有加载状态控制,会引发并发请求进一步加重问题。

解决方案

  1. 用useRef跟踪最新页码,避免闭包捕获旧值
  2. 添加加载状态,防止重复点击
  3. 使用函数式更新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>
    )
}

二、组件卡顿、无响应问题

问题原因

  1. PostCard的id属性写法错误:"post-item-" + post.id在JSX中会被解析为两个表达式,导致DOM元素id异常,引发不必要的重渲染或DOM操作开销
  2. 无限制重渲染:PostCard未做缓存优化,父组件更新时所有卡片都会重渲染;HandleLoadMore未用useCallback包裹,导致按钮每次都重渲染
  3. 冗余数据传递:API返回的帖子数据可能包含大量不必要字段,加重渲染负担
  4. session对象引用变化:服务器组件传递的整个session对象可能每次渲染都生成新引用,触发客户端组件重渲染

解决方案

  1. 修复PostCard的id属性,使用模板字符串拼接
  2. 用React.memo缓存PostCard组件,避免不必要重渲染
  3. 精简session传递,只传客户端需要的字段
  4. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 17:57:43