React:封装一个评论回复组件

发布于:2025-05-01 ⋅ 阅读:(43) ⋅ 点赞:(0)

分析

用户想要一个能够显示评论列表,并且允许用户进行回复的组件。可能还需要支持多级回复,也就是对回复进行再回复。然后,我要考虑组件的结构和功能。

首先,数据结构方面,评论应该包含id、内容、作者、时间,以及可能的子回复。子回复的结构可能和评论类似,所以可以设计一个通用的结构,比如每个评论有一个replies数组,里面存放回复对象。

接下来是组件的状态管理。评论列表应该用useState来管理,这样当添加新评论或回复时,组件可以重新渲染。还需要处理用户输入,所以需要状态来保存输入的文本。

然后是UI部分。评论和回复可能需要递归渲染,因为每个回复下面可能还有更多的回复。所以可以考虑用一个递归组件来渲染评论及其子回复。每个评论项需要显示作者、内容、时间,还有一个回复按钮。点击回复按钮时,要显示一个输入框,让用户输入回复内容。

输入框部分,可能需要根据当前回复的父评论ID来判断在哪里显示输入框。例如,当点击某个评论的回复按钮时,输入框应该出现在该评论的下方。因此,需要有一个状态来跟踪当前正在回复的评论ID。

提交回复的函数需要处理两种情况:一种是直接评论,另一种是回复某个具体的评论。提交时,需要更新评论列表,添加新的回复到对应的replies数组中。这里可能需要一个函数来递归查找父评论并添加回复,或者使用辅助函数来处理嵌套结构。

样式方面,用户可能希望有不同的缩进来表示层级关系。比如,顶级评论没有缩进,一级回复缩进一定距离,二级回复再缩进更多,以此类推。可以用paddingLeft或者marginLeft来实现,根据层级动态调整。

还需要考虑组件的可定制性,比如允许用户传入自定义样式、评论和回复的渲染方法,或者回调函数当提交评论时。这样组件更灵活,用户可以根据需要调整外观和行为。

测试一下功能,比如添加评论、回复评论,确保状态更新正确,UI渲染正确。特别是多级回复的情况,要确保递归渲染正确,并且输入框出现在正确的位置。

能的优化点:使用useCallback来避免函数重复创建,提高性能。特别是提交回复的函数和回复按钮的回调函数。另外,对于深层嵌套的评论,递归渲染可能会导致性能问题,但一般情况下的评论层级不会太深,所以应该可以接受。

最后,提供示例用法,让用户知道如何传入初始数据,以及如何使用组件。可能需要提醒用户根据需要调整样式,或者处理时间格式化等问题,比如使用date-fns或其他库来格式化时间显示。

实现代码

封装一个可复用的评论回复组件,支持多级嵌套回复。以下是代码实现:

import React, { useState } from 'react';
import PropTypes from 'prop-types';

// 评论项组件
const CommentItem = ({ 
  comment,
  depth = 0,
  onReply,
  activeReplyId,
  setActiveReplyId,
  parentAuthor 
}) => {
  const maxDepth = 4; // 控制最大嵌套深度
  const canReply = depth < maxDepth;

  return (
    <div 
      className="comment-item" 
      style={{ marginLeft: depth * 32 }}
    >
      <div className="comment-header">
        <span className="comment-author">{comment.author}</span>
        {parentAuthor && (
          <span className="reply-to">回复 @{parentAuthor}</span>
        )}
        <span className="comment-time">{comment.time}</span>
      </div>
      <div className="comment-content">{comment.content}</div>
      
      {canReply && (
        <button 
          className="reply-btn"
          onClick={() => setActiveReplyId(comment.id)}
        >
          回复
        </button>
      )}

      {/* 回复输入框 */}
      {activeReplyId === comment.id && (
        <CommentInput
          onSubmit={(content) => onReply(comment.id, content)}
          onCancel={() => setActiveReplyId(null)}
        />
      )}

      {/* 递归渲染子回复 */}
      {comment.replies?.map((reply) => (
        <CommentItem
          key={reply.id}
          comment={reply}
          depth={depth + 1}
          onReply={onReply}
          activeReplyId={activeReplyId}
          setActiveReplyId={setActiveReplyId}
          parentAuthor={comment.author}
        />
      ))}
    </div>
  );
};

// 评论输入框组件
const CommentInput = ({ onSubmit, onCancel }) => {
  const [content, setContent] = useState('');

  const handleSubmit = () => {
    if (content.trim()) {
      onSubmit(content);
      setContent('');
    }
  };

  return (
    <div className="comment-input-container">
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="输入评论内容..."
        rows={3}
      />
      <div className="input-buttons">
        <button onClick={handleSubmit}>提交</button>
        <button onClick={onCancel}>取消</button>
      </div>
    </div>
  );
};

// 主组件
const CommentComponent = ({ 
  initialComments,
  currentUser = '匿名用户'
}) => {
  const [comments, setComments] = useState(initialComments);
  const [activeReplyId, setActiveReplyId] = useState(null);

  // 添加评论/回复
  const addComment = (content, parentId = null) => {
    const newComment = {
      id: Date.now(),
      author: currentUser,
      content,
      time: new Date().toLocaleString(),
      replies: []
    };

    if (!parentId) {
      setComments([...comments, newComment]);
    } else {
      const updateComments = (comments) => 
        comments.map(comment => {
          if (comment.id === parentId) {
            return {
              ...comment,
              replies: [...comment.replies, newComment]
            };
          }
          return {
            ...comment,
            replies: updateComments(comment.replies)
          };
        });
      setComments(prev => updateComments(prev));
    }
  };

  return (
    <div className="comment-container">
      <CommentInput onSubmit={(content) => addComment(content)} />

      <div className="comments-list">
        {comments.map((comment) => (
          <CommentItem
            key={comment.id}
            comment={comment}
            onReply={addComment}
            activeReplyId={activeReplyId}
            setActiveReplyId={setActiveReplyId}
          />
        ))}
      </div>
    </div>
  );
};

// Prop 类型校验
CommentComponent.propTypes = {
  initialComments: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.isRequired,
      author: PropTypes.string.isRequired,
      content: PropTypes.string.isRequired,
      time: PropTypes.string,
      replies: PropTypes.array
    })
  ),
  currentUser: PropTypes.string
};

export default CommentComponent;

组件特性说明:

  1. 核心功能
  • 支持多级嵌套回复(默认最多4层)
  • 显示回复对象(回复 @xxx)
  • 支持自定义当前用户
  • 支持初始评论数据传入
  • 自动生成评论时间
  1. 组件结构
  • CommentComponent:主组件
  • CommentItem:单个评论项(支持递归渲染)
  • CommentInput:评论输入组件
  1. 使用示例
// 初始化数据示例
const initialComments = [
  {
    id: 1,
    author: '用户A',
    content: '第一条评论',
    time: '2023-08-01 10:00',
    replies: [
      {
        id: 2,
        author: '用户B',
        content: '第一条回复',
        time: '2023-08-01 10:05'
      }
    ]
  }
];

// 使用组件
<CommentComponent 
  initialComments={initialComments}
  currentUser="当前用户"
/>
  1. 样式建议
.comment-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.comment-item {
  margin: 16px 0;
  padding: 12px;
  border: 1px solid #eee;
  border-radius: 4px;
}

.comment-header {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
  color: #666;
}

.comment-author {
  font-weight: bold;
  margin-right: 8px;
}

.reply-to {
  color: #999;
  margin-right: 8px;
}

.comment-time {
  font-size: 0.9em;
}

.reply-btn {
  background: none;
  border: none;
  color: #1890ff;
  cursor: pointer;
  padding: 4px 8px;
}

.comment-input-container {
  margin: 16px 0;
}

.comment-input-container textarea {
  width: 100%;
  padding: 8px;
  margin-bottom: 8px;
}

.input-buttons button {
  margin-right: 8px;
  padding: 4px 12px;
}
  1. 扩展建议
  • 增加点赞功能
  • 支持富文本输入
  • 添加评论删除功能
  • 支持用户头像显示
  • 增加评论加载更多功能
  • 支持@用户功能

使用时请根据实际需求调整样式和功能实现,可以通过props传递更多配置项(如最大嵌套深度、日期格式等)来增强组件的灵活性。


网站公告

今日签到

点亮在社区的每一天
去签到