React - 将大型注释组件拆分为多个组件

时间:2017-08-02 09:27:59

标签: reactjs

我有一个很大的Comment组件,效果很好,但相当冗长。我最近向UI添加了一个报告按钮,该按钮切换reported状态,然后应该更改注释的输出(删除所有内容并显示报告的消息)。尝试在组件的返回方法中编写if语句让我意识到我应该将这个组件拆分(更不用说我发现自己在这个Comment组件之间复制/粘贴了大量代码非常相似的Reply组件。)

评论有3个主要观点' - 默认视图,报告的视图和我的评论'图。

每当我试图在过去分割组件时,我发现自己在向每个组件传递多个道具时陷入困境。我不确定我是做错了还是只是我需要习惯的东西。有关分割此组件的最佳方法的任何提示将不胜感激。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { replyToCommentService, deleteCommentService, reportCommentService } from '../../../services/CommentService';
import { likeService, removeLikeService } from '../../../services/LikeService';
import Reply from './Reply';
import Avatar from '../Avatars/Avatar';
import IconWithText from '../Icons/IconWithText';
import CommentForm from './CommentForm';
import Dropdown from '../Dropdowns/Dropdown';
import DropdownSection from '../Dropdowns/DropdownSection';

export default class Comment extends Component {
  constructor(props) {
    super(props);
    this.state = {
      replies: this.props.replies,
      showReply: false,
      reply: '',
      replyBtnDisabled: true,
      liked: this.props.liked,
      numberOfLikes: this.props.likes.length,
      moreActionsActive: false,
      reported: this.props.reported,
    };
  }

  handleInput = (reply) => {
    this.setState({ reply }, () => {
      this.fieldComplete();
    });
  }

  fieldComplete = () => {
    if (this.state.reply.length) {
      this.setState({ replyBtnDisabled: false });
    } else {
      this.setState({ replyBtnDisabled: true });
    }
  }

  toggleReply = () => {
    this.setState({ showReply: !this.state.showReply }, () => {
      if (this.state.showReply === true) {
        this.replyInput.focus();
      }
    });
  }

  postReply = () => {
    const data = { comment_id: this.props.id, comment_content: this.state.reply };
    replyToCommentService(data, this.postReplySuccess, this.error);
  }

  postReplySuccess = (res) => {
    this.setState({ replies: this.state.replies.concat(res.data) });
    this.toggleReply();
    this.handleInput('');
  }

  error = (res) => {
    console.log(res);
  }

  toggleLike = (e) => {
    e.preventDefault();
    const data = { model_id: this.props.id, model_type: 'comment' };
    if (this.state.liked) {
      removeLikeService(this.props.id, 'comment', this.removeLikeSuccess, this.error);
    } else {
      likeService(data, this.likeSuccess, this.error);
    }
  }

  likeSuccess = () => {
    this.toggleLikeState();
    this.setState({ numberOfLikes: this.state.numberOfLikes += 1 });
  }

  removeLikeSuccess = () => {
    this.toggleLikeState();
    this.setState({ numberOfLikes: this.state.numberOfLikes -= 1 });
  }

  toggleLikeState = () => {
    this.setState({ liked: !this.state.liked });
  }

  moreActionsClick = () => {
    this.setState({ moreActionsActive: !this.state.moreActionsActive });
  }

  deleteReply = (replyId) => {
    this.setState({ deletedReplyId: replyId });
    deleteCommentService(replyId, this.deleteReplySuccess, this.error);
  }

  deleteReplySuccess = () => {
    this.setState(prevState => ({ replies: prevState.replies.filter(reply => reply.id !== this.state.deletedReplyId) }));
  }

  ifEnterPressed = (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      this.postReply();
    }
  }

  reportComment = () => {
    const data = { model_id: this.props.id, model_type: 'comment' };
    reportCommentService(data, this.reportCommentSuccess, this.error);
  }

  reportCommentSuccess = (res) => {
    console.log(res);
  }

  render() {
    let repliesList;
    if (this.state.replies.length) {
      repliesList = (this.state.replies.map((reply) => {
        const { id, owner_id, content, owner_image_url, owner_full_name, ago, likes, liked } = reply;
        return (
          <Reply
            key={id}
            id={id}
            authorId={owner_id}
            title={content}
            image={owner_image_url}
            authorName={owner_full_name}
            timeSinceComment={ago}
            likes={likes}
            liked={liked}
            newComment={this.newCommentId}
            deleteReply={this.deleteReply}
          />
        );
      }));
    }

    const commentClass = classNames('comment-container', {
      'my-comment': this.props.myComment,
      'comment-reported': this.state.reported,
    });

    let likeBtnText;
    const numberOfLikes = this.state.numberOfLikes;
    if (numberOfLikes > 0) {
      likeBtnText = `${numberOfLikes} Likes`;

      if (numberOfLikes === 1) {
        likeBtnText = `${numberOfLikes} Like`;
      }
    } else {
      likeBtnText = 'Like';
    }

    const likeBtnClass = classNames('like-btn', 'faux-btn', 'grey-link', 'h5', {
      liked: this.state.liked,
    });

    let likeIconFill;
    if (this.state.liked) {
      likeIconFill = 'green';
    } else {
      likeIconFill = 'grey';
    }

    return (
      <li className={commentClass}>
        <div className="comment">
          <Avatar image={this.props.image} />

          <div className="body">
            <div className="header">
              <a href={`/user/${this.props.authorId}`} target="_blank" className="username green-link fw-medium">{this.props.authorName}</a>
              <span className="h5 text-grey">{this.props.timeSinceComment}</span>

              <Dropdown
                size="S"
                position="right"
                onClick={this.moreActionsClick}
                active={this.state.moreActionsActive}
                handleClickOutside={this.moreActionsClick}
                disableOnClickOutside={!this.state.moreActionsActive}
              >
                <DropdownSection>
                  {this.props.myComment &&
                    <button className="faux-btn dropdown-link" onClick={() => this.props.deleteComment(this.props.id)}>Delete comment</button>
                  }
                </DropdownSection>
                <DropdownSection>
                  <button className="faux-btn dropdown-link" onClick={() => this.reportComment(this.props.id)}>Report as inappropriate</button>
                </DropdownSection>
              </Dropdown>
            </div>

            <div className="comment-text"><p>{this.props.title}</p></div>

            <div className="actions">
              <button onClick={this.toggleLike} className={likeBtnClass}>
                <IconWithText text={likeBtnText} iconName="thumb-up" iconSize="S" iconFill={likeIconFill} />
              </button>

              <button onClick={this.toggleReply} className="reply-btn faux-btn grey-link h5">
                <IconWithText text="Reply" iconName="reply" iconSize="S" iconFill="grey" />
              </button>
            </div>
          </div>
        </div>

        {this.state.replies.length > 0 &&
          <div className="replies-container">
            <ul className="replies-list faux-list no-margin-list">
              {repliesList}
            </ul>
          </div>
        }

        {this.state.showReply &&
          <div className="reply-to-comment-form">
            <CommentForm
              commentContent={this.handleInput}
              postComment={(e) => { e.preventDefault(); this.postReply(); }}
              formDisabled={this.state.replyBtnDisabled}
              placeholder="Write a reply... press enter to submit"
              btnText="Reply"
              inputRef={(input) => { this.replyInput = input; }}
              handleKeyPress={this.ifEnterPressed}
            />
          </div>
        }
      </li>
    );
  }
}

Comment.propTypes = {
  id: PropTypes.number,
  authorId: PropTypes.number,
  title: PropTypes.string,
  image: PropTypes.string,
  authorName: PropTypes.string,
  timeSinceComment: PropTypes.string,
  likes: PropTypes.array,
  liked: PropTypes.bool,
  replies: PropTypes.array,
  myComment: PropTypes.bool,
  deleteComment: PropTypes.func,
  newCommentId: PropTypes.number,
  reported: PropTypes.bool,
};

1 个答案:

答案 0 :(得分:3)

一般问题是你的州居住的地方。

目前,您在组件(和/或服务)中拥有自己的状态,这使得拆分组件有点棘手,而且感觉不那么自然&#34;。

原因是每个自然子组件(例如回复列表或回复本身)需要该状态的部分,并且可能还需要修改该状态,然后由&#34完成;财产传递&#34;这可能很乏味。将状态的各个部分传递给子组件和/或传递事件回调,例如&#34; upDateState&#34; &#34; showThis&#34; &#34; showThat&#34 ;.

这有时是你想要的,你可以创建一个只呈现ui的无状态组件,例如答案列表。如果这是你想要的那么是的,你只需要习惯从父母那里传递道具。

继续增长应用程序的另一个答案是通过其状态对其进行建模,并且(正确地)执行该操作的唯一方法是将应用程序状态从组件中抽象出来。创建一个不在组件内部的状态,即每个组件都可以访问的状态。

您可能已经猜到我的建议是什么,看看Redux(或类似的状态管理库),您可以轻松地删除片段(组件)并将它们附加到Redux全局状态和操作。一旦你习惯了'#34;永远不会&#34;将应用程序状态保留在组件中,您将不会返回。 :)

PS! 这可能不是一个答案,但它的评论很长。只是想分享我的想法。