reactjs:setState在一个调用一次的函数中被调用两次?为什么?

tquggr8v  于 2022-11-22  发布在  React
关注(0)|答案(5)|浏览(210)

编辑:由于代码片段没有重现bug -这里有一个github repo的链接:(代码远未完成)
https://github.com/altruios/clicker-game
我现在已经在两台计算机上运行了它--两台计算机都有代码片段没有显示的相同行为。
第一个
所以我构建了一个clicker游戏来学习reaction,我不明白为什么这段代码会这样:
在主应用程序中,我有这个功能:

gainResource(event)
    {
    console.count("gain button");
    const name = event.target.name;
    this.setState( (prevState)=>
      {
      const newResources =  prevState.resources.map(resource=>
        {
        if(resource.name === name)  
          {
          resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
        }  
        return resource;
      });
      console.log(prevState.resources.find(item=>item.name===name).amount, "old");
      console.log(newResources.find(item=>item.name===name).amount, "new");
      return {resources: newResources}
    });
  }

这个console.count只运行一次...但是我得到了2个“旧的和新的”对。就好像setState在这个只运行一次的函数中运行了两次一样?
控制台输出为:

App.js:64 gain button: 1
App.js:76 1 "old"
App.js:77 1 "new"
App.js:76 2 "old"
App.js:77 2 "new"

所以看起来函数运行了一次。但是set状态运行了两次?
症状是它以2递增计数。而且amount的初始状态是0,而不是1,如gamedata.json中所示

resourceData:
        [
            {   
            name:"grey",
            resouceMax:100,
            isUnlocked:true,
            changePerTick:0,
            counterTillStopped:100,
            amount:0
            },{etc},{},{}],
        clickerData:
        [
            {
            name:"grey",
            subjectsOfIncrease:["grey"],
            isUnlocked:true,
            value:1
           },{etc},{},{}]

我不认为我接下来要写的代码与此行为相关,但我还不知道如何React,所以我不知道我错过了什么:但我是这样生成clicker按钮的:

const clickers = this.state.clickers.map(clickerData=>
      {
      return(
        <Clicker
          name={clickerData.name}  
          HandleClick = {this.gainResource}
          value = {clickerData.amount}
          key={clickerData.name}

        />

        )
    })

在clicker.js函数组件中,我只返回以下内容:

<div>
        {props.name}
        <button name={props.name} onClick={props.HandleClick}>
                {props.name} {props.value}
        </button>
        </div>

函数在构造函数中绑定到this...我不明白为什么在一个只调用一次的函数中运行两次setState。
我也试过:

<div>
        {props.name}
        <button name={props.name} onClick={()=>props.HandleClick}> //anon function results in no output
                {props.name} {props.value}
        </button>
        </div>
4c8rllxm

4c8rllxm1#

最佳答案:
我使用create-react-app,我的App Component被 Package 在Strict模式下...这会触发setState两次...这完美地解释了为什么这在代码片段中不可重现,以及为什么函数被调用一次,而setState被调用两次。
移除严格模式完全修复了该问题。

r6hnlfcb

r6hnlfcb2#

这是 Package 在****组件中的**setState(callback)**方法的预期行为<React.Strict>。

回调执行两次,以确保它不会直接改变状态。

  • 根据 github.com/facebook/react/issues/12856#issuecomment-390206425*

在程式码片段中,您会建立新的数组,但数组中的对象仍然相同:

const newResources = lastResources.map(resource => {
  if(resource.name === name){
    resource.amount = Number(resource.amount) + 1 
  }  
  return resource;
}

您必须单独复制每个对象:

const newResources = lastResources.map(resource => {
  const newObject = Object.assign({}, resource)
  if(resource.name === name){
    newObject.amount = Number(newObject.amount) + 1 
  }  
  return newObject;
}
xggvc2p6

xggvc2p63#

只要你没有给我们提供一个可运行的例子,我对可能发生的事情有一个疑问,让我们看看它是否有效。
我可以看到的是在*gainResource*函数中,特别是在这一行中*resource.amount = Number(resource.amount)+ 1*您正在尝试在不使用setState的情况下更新状态,这是React Documentation不推荐的
请改为尝试先指定
const myRessource = ressource
,然后再传回
myRessource

gainResource(event)
    {
    console.count("gain button");
    const name = event.target.name;
    this.setState( (prevState)=>
      {
      const newResources =  prevState.resources.map(resource=>
        {
       const myRessource = ressource;
        if(myRessource.name === name)  
          {
          myRessource.amount = Number(myRessource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
        }  
        return myRessource;
      });
      console.log(prevState.resources.find(item=>item.name===name).amount, "old");
      console.log(newResources.find(item=>item.name===name).amount, "new");
      return {resources: newResources}
    });
  }
ecr0jaav

ecr0jaav4#

好吧......所以在拔了一些头发之后......我发现了一种有效的方法......但我不认为这是“最佳实践”,但现在当我写这篇文章时,它对我很有效:

gainResource(event)
    {
    const name = event.target.name;

    const lastResources =  this.state.resources.slice();
      const newResources = lastResources.map(resource=>
        {
        if(resource.name === name)  
          {
          resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
        }  
        return resource;
      });

    this.setState({resources: newResources});
  }

对比

gainResource(event)
    {
    console.count("gain button");
    const name = event.target.name;
    this.setState( (prevState)=>
      {
      const newResources =  prevState.resources.map(resource=>
        {
        if(resource.name === name)  
          {
          resource.amount = Number(resource.amount) + 1 //Number(prevState.clickers.find(x=>x.name===name).value)
        }  
        return resource;
      });
      console.log(prevState.resources.find(item=>item.name===name).amount, "old");
      console.log(newResources.find(item=>item.name===name).amount, "new");
      return {resources: newResources}
    });
  }

没有prevState函数的setState只被调用一次...而有prevState函数的setState被调用两次...为什么?
所以我仍然不明白为什么setState使用一个带有prevState的函数会在一个只调用一次的函数中导致两次函数调用......我已经读到我应该使用prevstate作为this.state.resources.slice();只是拍了一个“不定时的快照”,可能是不可靠的。这是真的吗...还是这种方法可以接受?
这是一个答案,任何其他人挣扎与此。希望一个更好的答案可以张贴后,这个启示可能会发生什么。

nfg76nw0

nfg76nw05#

好吧,我花了一点时间来弄清楚这个问题。正如其他人提到的,你的回调函数必须是幂等的。这里要意识到的是,react每次调用它时都会将相同的状态对象示例传递到你的回调函数中。因此,如果你在第一次调用时更改了状态对象,那么在第二次调用时它将是不同的,你的回调函数将不是幂等的。

this.setState((state) =>
{
    //state.counter will have the same value on the first and second 
    //time your callback is called
    return { counter: state.counter + 1};
});

this.setState((state) =>
{
    //state.counter will have a value of n+1 the second time it is called 
    //because you are changing the sate object. This will have the net effect 
    //of incrementing state.counter by 2 each time you call this.setState!!
    state.counter = state.counter + 1;

    return { counter: state.counter};
});

上面的情况可能很明显,但在处理数组时,这种情况就不那么明显了。

this.setState((state) =>
{

    //even though we are creating a new array, the 
    //objects in the array have just been copied 
    //so changing them is probelmatic   
    newArray = [...state.someArray];    

    //this is ok as we are replacing the object at newArray[1]
    newArray[1] = {objectField : 1};

    //this is not ok
    newArray[1].objectField = 1;

    return { someArray: newArray};
});

相关问题