2017-08-02 64 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! 這可能不是一個答案,但它需要很長時間才能發表評論。只是想分享我的想法。