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

React嵌套评论组件实现回复及编辑功能的方案咨询

评论组件嵌套回复+编辑功能实现方案

核心思路

采用全局唯一ID+递归查找更新的方案替代下标路径定位,逻辑更简洁可维护,也避免了列表增删后下标错乱的问题。

具体改造步骤

  • 第一步:给所有层级评论补充全局唯一id,保证每条评论(不管嵌套层级)都有唯一标识
  • 第二步:封装通用递归更新函数,接收评论列表、目标id、更新回调,自动遍历找到目标评论后执行更新
  • 第三步:完善回复、编辑的交互逻辑,复用递归函数完成数据更新

完整改造后代码

import React, {useEffect, useState} from 'react';

// 初始化数据补充所有层级的唯一id
const initData = [
    {
        id: 1,
        comment: "Test Comment"
    },
    {
        id: 2,
        comment: "Second Comment",
        subComments: [
            {
                id: 21,
                comment: "Sub second comment 1"
            },
            {
                id: 22,
                comment: "Sub second comment 2"
            },
            {
                id: 23,
                comment: "Sub second comment 3"
            }
        ]
    },
    {
        id: 3,
        comment: "How much is 2 + 2?",
        subComments: [
            {
                id: 31,
                comment: "It is 5",
                subComments: [
                    {
                        id: 311,
                        comment: "No, it's 4."
                    }
                ],
            },
            {
                id: 32,
                comment: "4!",
            },
            {
                id: 33,
                comment: "Is this a joke?"
            }
        ]
    }
]

// 全局自增id生成器,保证新增评论id唯一
let autoIncrementId = 1000;

const Comment = () => {
    const [ comments, setComments ] = useState([])
    const [ commentText, setCommentText ] = useState("");
    // 编辑状态:当前正在编辑的评论id+编辑内容
    const [ editId, setEditId ] = useState(null);
    const [ editText, setEditText ] = useState("");

    useEffect(() => {
        setComments(initData);
    }, []);

    const handleChangeCommentText = (event) => {
        setCommentText(event.target.value);
    }

    // 递归更新工具函数:找到指定id的评论,执行更新操作
    const updateCommentById = (commentList, targetId, updateFn) => {
        return commentList.map(comment => {
            // 找到目标评论,执行更新
            if (comment.id === targetId) {
                return updateFn(comment);
            }
            // 有子评论则递归遍历子评论
            if (comment.subComments?.length) {
                return {
                    ...comment,
                    subComments: updateCommentById(comment.subComments, targetId, updateFn)
                }
            }
            return comment;
        })
    }

    // 新增一级评论
    const handleAddComment = () => {
        if(commentText === '') {
            alert('Please, enter a comment.');
            return;
        }
        const newComment = {
            id: autoIncrementId++,
            comment: commentText
        }
        setComments(prev => [...prev, newComment]);
        setCommentText("");
    }

    // 回复评论(任意层级都可以用)
    const handleReplyComment = (targetId) => {
        if(commentText === '') {
            alert('Please, enter a comment');
            return;
        }
        const newReply = {
            id: autoIncrementId++,
            comment: commentText
        }
        setComments(prev => {
            return updateCommentById(prev, targetId, (comment) => {
                return {
                    ...comment,
                    subComments: [...(comment.subComments || []), newReply]
                }
            })
        })
        setCommentText("");
    }

    // 触发编辑
    const handleStartEdit = (comment) => {
        setEditId(comment.id);
        setEditText(comment.comment);
    }

    // 保存编辑
    const handleSaveEdit = () => {
        if (editText === '') {
            alert('评论内容不能为空');
            return;
        }
        setComments(prev => {
            return updateCommentById(prev, editId, (comment) => {
                return {
                    ...comment,
                    comment: editText
                }
            })
        })
        setEditId(null);
        setEditText("");
    }

    // 递归渲染子评论
    const showSubComments = (subComments = [], margin) => {
        return subComments.map((comment) => {
            return (
                <div key={comment.id} style={{marginTop: '8px'}}>
                    <span style={{marginLeft: `${margin}rem`}}>
                        {editId === comment.id ? (
                            <>
                                <input 
                                    value={editText} 
                                    onChange={(e) => setEditText(e.target.value)} 
                                    style={{marginRight: '8px'}}
                                />
                                <button onClick={handleSaveEdit}>保存</button>
                                <button onClick={() => setEditId(null)}>取消</button>
                            </>
                        ) : (
                            <>
                                {comment.comment} 
                                <button type="button" onClick={() => handleReplyComment(comment.id)} style={{margin: '0 4px'}}>回复</button>
                                <button type="button" onClick={() => handleStartEdit(comment)}>编辑</button>
                            </>
                        )}
                    </span>
                    {showSubComments(comment.subComments || [], margin+2)}
                </div>
            )
        });
    }

    return (
        <div style={{padding: '16px'}}>
            <div>
                <textarea 
                    type="textarea" 
                    value={commentText} 
                    rows={5} 
                    cols={50} 
                    onChange={handleChangeCommentText}
                    placeholder="输入评论内容..."
                    style={{display: 'block', marginBottom: '8px'}}
                />
                <button type="button" onClick={handleAddComment}>新增一级评论</button>
            </div>
            <div style={{marginTop: '24px'}}>
                {comments.map((comment) => {
                    return (
                        <div key={comment.id} style={{marginBottom: '12px'}}>
                            {editId === comment.id ? (
                                <>
                                    <input 
                                        value={editText} 
                                        onChange={(e) => setEditText(e.target.value)} 
                                        style={{marginRight: '8px'}}
                                    />
                                    <button onClick={handleSaveEdit}>保存</button>
                                    <button onClick={() => setEditId(null)}>取消</button>
                                </>
                            ) : (
                                <>
                                    <span>{comment.comment} </span>
                                    <button type="button" onClick={() => handleReplyComment(comment.id)} style={{margin: '0 4px'}}>回复</button>
                                    <button type="button" onClick={() => handleStartEdit(comment)}>编辑</button>
                                </>
                            )}
                            {showSubComments(comment.subComments || [], 2)}
                        </div>
                    )
                })}
            </div>    
        </div>
    );
}

export default Comment;

方案优势

  • 不需要维护复杂的下标路径,不管嵌套多少层级,只要拿到目标评论id就能完成更新
  • 递归函数复用性高,新增回复、编辑评论两个功能都可以复用同一个updateCommentById方法
  • 后续如果要新增删除评论功能,同样可以复用这个递归函数,只需要修改更新逻辑即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:06:05