2017-08-02 35 views
0

我有一个很大的Comment组件,效果很好,但相当长。我最近在UI中添加了一个报告按钮,用于切换reported状态,该状态应该改变评论的输出(删除所有内容并显示报告的消息)。试图在组件的返回方法中写if语句让我意识到我应该分解这个组件(更不用说我已经发现自己复制/粘贴了这个组件和非常类似的Reply组件之间的很多代码)。React - 将大型评论组件拆分为多个组件

该评论有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, 
}; 

回答

2

那么一般问题是你的国家生活的地方。

当前你在组件(和/或服务)中拥有你的状态,这使得组件分裂有点棘手,并且感觉不那么“自然”。

原因是每个自然的子组件(例如回复列表或回复本身)都需要该状态的片段,并且可能还需要修改该状态,然后由“属性传递”完成并且它可能很乏味。将状态片段传递给子组件和/或传递事件回调,比如“upDateState”“showThis”“showThat”。

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

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

您可能已经猜到我现在的建议是什么,看看Redux(或类似的状态管理库),并且可以轻松剪切(组件)并将它们附加到Redux全局状态和操作。一旦你习惯了“永不”保持你的组件的应用程序状态,你就不会回头。 :)

PS! 这可能不是一个答案,但它需要很长时间才能发表评论。只是想分享我的想法。