我有一个很大的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,
};
答案 0 :(得分:3)
一般问题是你的州居住的地方。
目前,您在组件(和/或服务)中拥有自己的状态,这使得拆分组件有点棘手,而且感觉不那么自然&#34;。
原因是每个自然子组件(例如回复列表或回复本身)需要该状态的部分,并且可能还需要修改该状态,然后由&#34完成;财产传递&#34;这可能很乏味。将状态的各个部分传递给子组件和/或传递事件回调,例如&#34; upDateState&#34; &#34; showThis&#34; &#34; showThat&#34 ;.
这有时是你想要的,你可以创建一个只呈现ui的无状态组件,例如答案列表。如果这是你想要的那么是的,你只需要习惯从父母那里传递道具。
继续增长应用程序的另一个答案是通过其状态对其进行建模,并且(正确地)执行该操作的唯一方法是将应用程序状态从组件中抽象出来。创建一个不在组件内部的状态,即每个组件都可以访问的状态。
您可能已经猜到我的建议是什么,看看Redux(或类似的状态管理库),您可以轻松地删除片段(组件)并将它们附加到Redux全局状态和操作。一旦你习惯了'#34;永远不会&#34;将应用程序状态保留在组件中,您将不会返回。 :)
PS! 这可能不是一个答案,但它的评论很长。只是想分享我的想法。