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

Next.js中如何从子组件更新状态实现评论无刷新更新?

问题

在我的Next.js应用中,我使用getServerSideProps为[url].tsx路由从数据库拉取数据(url对应数据库中的特定项目),并通过{project.name}等填充页面内容。页面下方有两个区域:添加评论区(含提交表单子组件)和查看评论区(评论存储在包含评分、名称、内容、关联项目ID的表中,含评论统计子组件,显示评论总数和项目平均评分)。

我希望在提交评论后无需刷新页面,就能更新平均评分并显示新评论。我尝试使用React的useState初始化状态:

const [comments, setComments] = useState(project.comments)

但因组件拆分,我在CommentForm组件提交数据到API路由后,不清楚如何将setComments传递给CommentStats和CommentGrid组件,进而更新状态,这让我感到困惑。

附上相关组件代码:

CommentStats.tsx

type CommentStatsProps = {
    comments: any;
}

const CommentStats = ({ comments }: CommentStatsProps) => {
    const totalComments = comments.length;
    const totalStars = Comments.reduce((acc: any, curr: { rating: any; }) => acc + curr.rating, 0);
    const averageStars = comments / totalComments;
    
    return (
        <div className="max-w-4xl mx-auto flex justify-between items-center p-8 my-12 bg-white rounded-lg shadow-lg">
            <div className="flex items-center p-4 bg-violet-500 rounded-lg space-x-4">
                <h1 className="text-4xl font-bold text-white">{averageStars.toFixed(1)}</h1>
                <FaStar className="h-5 text-3xl text-yellow-300" />
            </div>
            <div className="flex items-center p-4 bg-slate-500 rounded-lg space-x-4">
                <p className="text-2xl text-white">
                    <span className="text-4xl font-bold">{totalComments}</span> {totalComments === 1 ? "Comment" : "Comments"}
                </p>
            </div>
        </div>
    );
}

export default CommentStats

CommentGrid.tsx

import CommentCard from "@/components/Comments/CommentCard";

/* eslint-disable @typescript-eslint/no-explicit-any */
type CommentsProps = {
    comments: any;
}

type CommentProps = {
    id: string;
    name: string;
    comment: string;
    rating: number;
    createdAt: string;
}

const CommentGrid = ({ comments }: CommentsProps) => {

    const totalComments = comments.length;

    const left = comments.slice(0, Math.ceil(totalComments / 2));
    const right = comments.slice(Math.ceil(totalComments / 2), totalComments);

    const list = {
        visible: { 
            opacity: 1,
            y: 0,
            transition: {
                when: "beforeChildren",
                staggerChildren: 0.5
            }
         },
        hidden: { opacity: 0, y: -100 }
    }

    const item = {
        visible: { opacity: 1, y: 0 },
        hidden: { opacity: 0, y: -100 }
    }

    // Check if there are any comments, if not return a message
    if (totalComments === 0) {
        return (
            <div className="tracking-wider text-center">
                <h1 className="text-4xl pb-4">No Comments Yet</h1>
                <p className="text-2xl">Be the first to leave a comment!</p>
            </div>
        )
    }

    return (
        
        <motion.div
            // initial={{ opacity: 0 }}
            // animate={{ opacity: 1 }}
            // whileInView={{ opacity: 1 }}
            // transition={{ duration: 0.5 }}
            // viewport={{ once: true}}
            className="grid md:grid-cols-2 gap-8">
        
            {/* Left */}
            <motion.ul
                whileInView={{ opacity: 1 }}
                initial="hidden"
                animate="visible"
                variants={list}
                className="space-y-8">
                {left.map((comment: CommentProps) => (
                    <motion.li 
                        variants={item}
                        key={comment.id}
                    >

                        <CommentCard stars={comment.rating} author={comment.name} comment={comment.comment} date={comment.createdAt} />
                    </motion.li>
                ))}
            </motion.ul>

        </motion.div>

    )

}

export default CommentGrid

我不想把实现复杂化,但希望在API后台处理评论创建时实现实时更新,请问有没有简单的解决方法?


简单解决方案

1. 将状态提升到父组件([url].tsx)

把comments状态和setComments放在最顶层的页面组件中,将comments传递给CommentStats和CommentGrid,同时给CommentForm传递回调函数,用于提交成功后更新状态。

示例父组件代码:

// [url].tsx
import { useState } from 'react';
import CommentForm from '@/components/Comments/CommentForm';
import CommentStats from '@/components/Comments/CommentStats';
import CommentGrid from '@/components/Comments/CommentGrid';

export async function getServerSideProps(context) {
  // 原有数据拉取逻辑,获取project及关联评论
  const project = await fetchProjectByUrl(context.params.url);
  return { props: { project } };
}

export default function ProjectPage({ project }) {
  const [comments, setComments] = useState(project.comments);

  // 定义提交回调,传给CommentForm
  const handleCommentSubmit = async (newCommentData) => {
    try {
      // 调用API提交评论
      const res = await fetch('/api/comments', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...newCommentData, projectId: project.id })
      });
      
      if (res.ok) {
        // 获取API返回的完整新评论(含数据库生成的id、createdAt等)
        const newComment = await res.json();
        // 将新评论添加到列表头部,更新本地状态
        setComments(prev => [newComment, ...prev]);
      }
    } catch (err) {
      console.error('提交评论失败:', err);
    }
  };

  return (
    <div>
      <h1>{project.name}</h1>
      {/* 其他项目内容 */}

      {/* 添加评论区 */}
      <CommentForm onSubmit={handleCommentSubmit} />

      {/* 评论统计区 */}
      <CommentStats comments={comments} />

      {/* 评论列表区 */}
      <CommentGrid comments={comments} />
    </div>
  );
}

2. 修改CommentForm接收并调用回调

在CommentForm中,表单提交成功后调用父组件传入的onSubmit回调,传递新评论数据:

// CommentForm.tsx
import { useState } from 'react';

export default function CommentForm({ onSubmit }) {
  const [name, setName] = useState('');
  const [comment, setComment] = useState('');
  const [rating, setRating] = useState(5);

  const handleFormSubmit = async (e) => {
    e.preventDefault();
    // 调用父组件回调,传递表单数据
    await onSubmit({ name, comment, rating });
    // 重置表单
    setName('');
    setComment('');
    setRating(5);
  };

  return (
    <form onSubmit={handleFormSubmit} className="space-y-4">
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="你的名字"
        required
        className="w-full p-2 border rounded"
      />
      <textarea
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        placeholder="写下你的评论"
        required
        rows={4}
        className="w-full p-2 border rounded"
      />
      <div className="flex space-x-2">
        {[1,2,3,4,5].map(num => (
          <button
            type="button"
            onClick={() => setRating(num)}
            className={`px-3 py-1 rounded ${rating === num ? 'bg-yellow-400 text-white' : 'bg-gray-100'}`}
            key={num}
          >
            {num}星
          </button>
        ))}
      </div>
      <button type="submit" className="w-full p-3 bg-violet-500 text-white rounded">
        提交评论
      </button>
    </form>
  );
}

3. 修复CommentStats的代码错误

原组件存在两处逻辑错误,修正后才能正确计算平均评分:

// CommentStats.tsx
import { FaStar } from 'react-icons/fa';

type CommentStatsProps = {
    comments: Array<{ rating: number }>;
}

const CommentStats = ({ comments }: CommentStatsProps) => {
    const totalComments = comments.length;
    // 修正:将Comments改为comments(小写开头)
    const totalStars = comments.reduce((acc: number, curr) => acc + curr.rating, 0);
    // 修正:用totalStars除以totalComments,处理0评论避免NaN
    const averageStars = totalComments > 0 ? totalStars / totalComments : 0;
    
    return (
        <div className="max-w-4xl mx-auto flex justify-between items-center p-8 my-12 bg-white rounded-lg shadow-lg">
            <div className="flex items-center p-4 bg-violet-500 rounded-lg space-x-4">
                <h1 className="text-4xl font-bold text-white">{averageStars.toFixed(1)}</h1>
                <FaStar className="h-5 text-3xl text-yellow-300" />
            </div>
            <div className="flex items-center p-4 bg-slate-500 rounded-lg space-x-4">
                <p className="text-2xl text-white">
                    <span className="text-4xl font-bold">{totalComments}</span> {totalComments === 1 ? "Comment" : "Comments"}
                </p>
            </div>
        </div>
    );
}

export default CommentStats

方案优势

  • 无需引入复杂状态管理库,仅用React内置API实现
  • 组件职责清晰:父组件管理状态,子组件专注展示和交互
  • 提交成功后直接更新本地状态,无需重新拉取整个评论列表,性能更优

内容的提问来源于stack exchange,提问作者Louis Stephens

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 19:50:24