为什么这个 onClick 事件处理程序在我的 create-react-app 中触发两次

Why is this onClick event handler firing twice in my create-react-app

有人能告诉我为什么这个“upvote”onClick 处理程序会触发两次吗? 日志将表明它只有 运行 一次,但它控制的分数增加了 2

export default class Container extends Component {
  constructor(props) {
    super(props);

    this.state = {
      jokes: [],
    };
    this.getNewJokes = this.getNewJokes.bind(this);
    this.retrieveJokes = this.retrieveJokes.bind(this);
    this.upVote = this.upVote.bind(this);
  }
upVote(id) {
    this.setState(state => {
      //find the joke with the matching id and increase score by one
      const modifiedJokes = state.jokes.map(joke => {
        if (joke.id === id) {
          
          joke.score = joke.score + 1;
          
        }
        return joke;
      });
      console.log(modifiedJokes);
      return { jokes: modifiedJokes };
    });
  }
render() {
    return (
      <div>
        <h1>Container</h1>
        {this.state.jokes.map(joke => (
          <Joke
            key={joke.id}
            id={joke.id}
            joke={joke.joke}
            score={joke.score}
            upVote={this.upVote}
            downVote={this.downVote}
          />
        ))}
      </div>
    );
  }
}

另一方面,如果我以这种方式重写处理程序,那么它只会触发一次

upVote(id) {
    const modifiedJokes = this.state.jokes.map(joke => {
      if (joke.id === id) {
        joke.score = joke.score + 1;
      }
      return joke;
    });
    this.setState({ jokes: modifiedJokes });
  };

这行不通,因为 React 使用合成事件,这些事件在处理完成后会被重用。调用 setState 的函数形式将延迟评估(当 setState 调用依赖于事件并且事件已失去其值时,这可能很危险)。

所以这也应该有效:

this.setState((state,props) => ({ jokes: modifiedJokes}));

我现在找不到我的来源,但是,我记得不久前遇到过这个。我确信浏览 React 文档可以提供更深入的解释

我最好的猜测是,在第一种情况下,您也在直接修改状态,当您执行 joke.score = joke.score + 1;

因为您直接在状态数组变量上进行此映射,而在 Javascript 中,当使用数组时,您仅使用指向该数组的指针,而不是创建该数组的副本。

所以映射函数可能采用数组的浅表副本,这就是问题所在。

您可以在使用 lodash 之前创建状态数组的深层副本,这不会导致您的问题:

https://codesandbox.io/s/great-babbage-lorlm

我发现了问题所在。我会 post 一个答案,以防万一有人在遇到同样的问题时偶然发现这个问题。

在调试模式下,React 会 运行 某些功能两次,以阻止某些可能导致错误的操作。这些操作之一是直接修改状态。 做的时候

this.setState(state => {
      //find the joke with the matching id and increase score by one
      const modifiedJokes = state.jokes.map(joke => {
        if (joke.id === id) {
          
          joke.score = joke.score + 1;
          
        }
        return joke;
      });
      console.log(modifiedJokes);
      return { jokes: modifiedJokes };

映射函数returns一个新数组,其中每个元素都指向原始数组中的相同元素。因此通过


state.jokes.map(joke => {
        if (joke.id === id) {          
          joke.score = joke.score + 1;          
        }

我实际上是在直接修改状态。在调试模式下响应 运行 setstate 函数两次以清除此类操作。

所以修改嵌套在数组中的对象的属性的正确“反应方式”是改为这样做

this.setState(state =>{
   let modifiedJokes = state.jokes.map(joke => {
        
        if (joke.id === id) {          
          return {...joke, score:joke.score+1}         
        }
        else{ return joke}
   })
   return {jokes:modifiedJokes}
}) 

这样,当遇到具有正确 ID 的元素时,会对该特定元素进行复制,并且该副本的分数会增加一个,这不会影响仍处于未触及状态的实际元素,直到它通过提交新状态的反应来修改