我正在尝试为React中列表中的每个项目创建一个计数器。我希望根据用户单击的内容分别增加或减少。此问题是所有计数器的点击次数都会增加和减少 单个元素,但是我只希望更改单击元素的计数器。
这是我的代码:
class name extends Component {
constructor(){
super()
this.state = {
news: [],
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
我正在尝试这样做:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
但是它不起作用也尝试了this.onVote(item.i)
并且结果相同
答案 0 :(得分:2)
我真的看不到您希望如何将投票作为本地组件状态的一部分,因为它确实与您可以对其进行投票的实体有关(我认为)。
因此,如果我是您,我将重写代码略有不同。由于我不知道您以后打算如何进行表决(这就像一个实时过程,或者至少是一种保存按钮,因为它以本地VotingApp
状态保存在这里),我只是将所有内容保存到本地状态,您将如何处理这并不是我真正想回答的。
因此,就我个人而言,我宁愿选择一个功能组件,仅呈现新闻项及其投票功能,其中voteCount
是项实体的一部分。如果这不是您接收数据的方式,则没有什么会阻止您在获取之后并真正在屏幕上显示之前添加数据。应用程序本身将接收更改,更改的项目以及此后的操作将完全由您决定;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = [] } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn\'t want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
答案 1 :(得分:1)
单击所有项目时,所有项目计数发生变化的原因是,它们在voteing
组件的状态下都共享相同的投票计数值name
。
要解决此问题,您应该将每个项目分成其自己的有状态组件。这样每个人都可以跟踪自己的点击次数。
例如:
class name extends Component {
constructor(){
super();
this.state = {
news: []
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
您还可以为父组件中的每个项目维护单独的计数,但我发现将其拆分为单独的组件会更加简洁。
答案 2 :(得分:1)
我注意到一些与您的问题无关的事情。
1)onVoting
应该绑定在您的构造函数中或使用onVoting = () => { ..... }
2)在呈现函数中,您拥有onVote
而不是onVoting
关于您的主要问题,在您的状态下,您只维护一个针对所有新闻元素显示和更改的计数器。解决此问题的一种简单方法是为每篇新闻创建一个新的react元素,以处理每篇文章的投票。
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
关于为什么不应该绑定到渲染函数的一些背景知识。 https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
原因:父组件正在向下传递箭头功能 道具。箭头功能在每个渲染器上重新分配(相同的故事 与使用绑定)。因此,尽管我已经将User.js声明为 PureComponent,用户父级中的箭头功能会导致用户 组件,以查看正在向所有用户发送的新功能。 因此,每个用户在单击任何删除按钮时都会重新渲染。
这也是为什么您不应该在React中使用索引作为键。 https://reactjs.org/docs/lists-and-keys.html
如果项的顺序可能不建议使用索引作为键 更改。这会对性能产生负面影响,并可能导致问题 具有组件状态。查阅Robin Pokorny的文章,了解 关于使用索引作为索引的负面影响的深入解释 键。如果您选择不为列表项分配显式键,则 React将默认使用索引作为键。
这里是有关为什么您需要输入键的深入说明 有兴趣了解更多。