以下是我的情景:我有一个包含问题列表的Feed。在每个问题中,我都有一个upvote按钮,用于提出这个问题。
因此,我将设计一个在其他问题组件之间共享的upvote按钮组件。单击此按钮时,按钮将触发事件到服务器。方法签名有时候如:
func upvote(questionOwnerId, upvoteUserId) { ... }
在upvoting之后,upvote将分别为redux句柄和更新状态发送一个事件。所以upvote按钮将始终显示最新状态。 (该问题的用户数量有多少)。
这是我的问题:在调用方法到服务器然后在redux树上触发唯一 redux路径后,我不知道如何制作共享组件(upvote组件)
我有一个解决方案:Upvote组件将收到以下参数:
然后upvote函数和Reducer将分别在redux树上使用redux path for update state。这种方法是否运作良好,看起来干净的项目?或者我的问题是否有“真正的”方式?
@Edit: Redux路径,例如:feeds.item.questionList.question[0]
,用于0索引处的问题组件。 {1}用于1索引处的问题组件...因此Reducer可以了解如何更新redux树。
答案 0 :(得分:3)
这样的共享组件可以通过接收正确的props
并传递通用onClick
事件来完成。
例如,假设我们创建名为<Question />
的组件,它将是道具:
votes
- 显示当前的投票数。onUpvote
- 作为向上投票按钮的点击事件。onDownvote
- 作为向下投票按钮的点击事件。id
- 它需要id
才能将其传递给父母&#39; s
功能question
- 要显示的问题的值。现在,您的父组件将从questions
中获取redux-store
的列表,并在其上.map
,并为每个问题返回一个<Question />
组件道具(id
,votes
和question
)
对于onUpVote
和onDownVote
,您将传递在父级中创建的函数。这些函数将调度操作,并由reducer处理,它将返回新状态,这将触发重新渲染,显示新数据。
我已经创建了一个简单的示例,请注意我在这里不能使用redux
所以我在App
组件中管理了状态,但我在评论中提到了你可以调度动作以及减压器内部的逻辑应该是什么。
const questionsList = [
{
id: 1,
votes: 2,
question: "whats up"
},
{
id: 2,
votes: -1,
question: "whats the time"
},
{
id: 3,
votes: 0,
question: "where are you"
},
{
id: 4,
votes: 7,
question: "who are you"
}
];
class Question extends React.Component {
constructor(props) {
super(props);
this.onUpvote = this.onUpvote.bind(this);
this.onDownvote = this.onDownvote.bind(this);
}
onUpvote() {
const { id, onUpvote } = this.props;
onUpvote(id);
}
onDownvote() {
const { id, onDownvote } = this.props;
onDownvote(id);
}
render() {
const { votes, question } = this.props;
const voteClassName = `votes ${votes < 0 ? 'low' : 'high'}`;
return (
<div className="question-wrapper">
<div className="buttons-wrapper">
<button className="button" onClick={this.onUpvote}>+</button>
<div className={voteClassName}>{votes}</div>
<button className="button" onClick={this.onDownvote}>-</button>
</div>
<div className="question">{question}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
questions: questionsList
};
this.onUpvote = this.onUpvote.bind(this);
this.onDownvote = this.onDownvote.bind(this);
}
onUpvote(id) {
const { questions } = this.state;
// you can dispatch an action here
// and instead of doing this logic in here you can do it in your reducer
const nextState = questions.map(question => {
if (question.id != id) {
return question;
}
return {
...question,
votes: question.votes + 1
};
});
this.setState({ questions: nextState });
}
onDownvote(id) {
const { questions } = this.state;
// you can dispatch an action here
// and instead of doing this logic in here you can do it in your reducer
const nextState = questions.map(question => {
if (question.id != id) {
return question;
}
return {
...question,
votes: question.votes - 1
};
});
this.setState({ questions: nextState });
}
render() {
const { questions } = this.state; // get the questions via props (redux store)
return (
<div>
{questions.map(q => (
<Question
id={q.id}
question={q.question}
votes={q.votes}
onUpvote={this.onUpvote}
onDownvote={this.onDownvote}
/>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
&#13;
.question-wrapper{
display: flex;
flex-direction: row;
width: 150px;
box-shadow: 0 0 2px 1px #333;
margin: 10px 0;
padding: 5px 20px;
}
.buttons-wrapper{
display:flex;
flex-direction: column;
}
.button{
margin: 10px 0;
cursor: pointer;
}
.question{
align-self: center;
margin: 0 20px;
font-size: 22px;
}
.high{
color: #3cba54;
}
.low{
color: #db3236;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
&#13;