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
相关产品推荐
相关产品推荐

