【问题标题】:reactjs: setState being called twice in a function called once? why?reactjs:setState 在一个调用一次的函数中被调用两次?为什么?
【发布时间】:2020-05-30 17:58:58
【问题描述】:

编辑:由于代码片段不会重现错误 - 这是 github 存储库的链接:(代码远未完成)

https://github.com/altruios/clicker-game

我现在已经在两台计算机上运行它 - 两者都具有代码片段未显示的相同行为。

//interestingly enough, this works just fine, where the same code I run locally has the doubling.
//when I comment out ALL other code except for this code I STILL get the error locally
//at this point the only difference is import export of components... here they are in one file.
//below is original code from file (
/* 
FILE::::Clicker.js
 
import React from 'react';

function Clicker(props)	
	{
	return(
		<div>
		{props.name}
		<button 
			name={props.name} 
			onClick={props.HandleClick} 
			data-target={props.subjectsOfIncrease}> 
				{props.name} {props.value}
	
		</button>
		</div>


		)
}

export default Clicker;

FILE:: Resouce.js

import React from 'react';
function Resource(props)	
	{
	return(
		<div>
		{props.name} and  {props.amount || 0}

		</div>


		)
}

export default Resource;



*/
//besides the import/export and seprate files - code is the same. it works in here, does not work locally on my machine.

const gameData = {
  clickerData: [{
    name: "grey",
    subjectsOfIncrease: ["grey"],
    isUnlocked: true,
    value: 1
  }],
  resourceData: [{
    name: "grey",
    resouceMax: 100,
    isUnlocked: true,
    changePerTick: 0,
    counterTillStopped: 100,
    amount: 0
  }]
}
class App extends React.Component {
    constructor() {
      super();
      this.state = {
        resources: gameData.resourceData,
        clickers: gameData.clickerData
      };
      this.gainResource = this.gainResource.bind(this);
    }
    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
        }
      });
    }
    render() {
      const resources = this.state.resources.map(resourceData => {
          return (
            <Resource 
              name = {resourceData.name}
              resouceMax = {resourceData.resourceMax}
              isUnlocked = {resourceData.isUnlocked}
              changePerTick = {resourceData.changePerTick}
              counterTillStopped = {resourceData.countTillStopped}
              amount = {resourceData.amount}
              key = {resourceData.name}
            />
          )
      })

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

    return (
          <div className = "App" > 
            {resources} 
           {clickers} 
          </div>
   )
 }
}
function Resource(props) {
      return  <div >  {props.name} and {props.amount || 0} </div>
}

function Clicker(props) {
      return ( 
        <div > {props.name} 
          <button name = {props.name}  onClick = {props.HandleClick}>
            {props.name} {props.value}
          </button> 
        </div>
     )
}
const root = document.getElementById('root');
ReactDOM.render(  <App / >,root );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

所以我正在构建一个点击游戏来学习反应,我不明白为什么这段代码会这样:

在主应用中我有这个功能:


  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 在这个只运行一次的函数中运行了两次?

console.output 是:

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"

所以看起来函数运行了一次。但是设置状态正在运行两次?

症状是它增加了 2。而且数量的初始状态是 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},{},{}]


我不认为我要处理的其余代码与这种行为有关,但我还不知道反应,所以我不知道我错过了什么:但这就是我正在生成点击器按钮:

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...我不明白为什么 this 在一个调用一次的函数中运行 setState 两次。

我也试过了:


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

【问题讨论】:

  • 有趣。您是否会使用可运行的minimal reproducible example 来更新您的问题,使用 Stack Snippets([&lt;&gt;] 工具栏按钮)演示问题。 Stack Snippets 支持 React,包括 JSX; here's how to do one。这将帮助人们看到问题并帮助他们帮助你。
  • 添加了代码片段:但是这并没有在代码片段中重现......这让我发疯了我已经注释掉了所有其他代码,除了该代码片段中的内容和我的机器上的行为保持不变(双重调用),但它在代码片段中工作正常继续撕掉头发感谢代码片段的提示......第一次使用它......到目前为止我唯一线索是代码片段和我的代码之间的区别:这是因为代码被分成几个文件以进行组织,而不是像代码片段中那样的一个主文件......仍然不知道问题是什么。 ..
  • 添加一个指向 git-hub 的链接,以便人们可以在他们的机器上本地运行它 - 看看问题可能是什么......因为代码片段没有重现错误。

标签: javascript reactjs setstate


【解决方案1】:

这是包装在 组件中的 setState(callback) 方法的预期行为。

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

根据https://github.com/facebook/react/issues/12856#issuecomment-390206425


在sn-p中,你新建了一个数组,但是里面的对象还是一样的:

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;
}

【讨论】:

【解决方案2】:

最佳答案:

我使用的是 create-react-app。并且我的 App 组件被包装在 Strict 模式下......它会触发 setState 两次......这完美地解释了为什么这在代码片段上无法重现,以及为什么函数被调用一次,而 setState 被调用了两次。

删除严格模式完全解决了这个问题。

【讨论】:

    【解决方案3】:

    只要您没有向我们提供可运行的示例,我就会怀疑会发生什么,让我们看看它是否有效。

    我可以在 gainResource 函数中看到,特别是在这一行 resource.amount = Number(resource.amount) + 1 你试图更新状态而不使用 React Documentation 不推荐的 setState

    请先尝试分配一个 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}
        });
      }
    

    【讨论】:

    • this.setState( (prevState)=> { const newResources = prevState.resources.map(resource=> { const tempResource = resource; if(resource.name === name) { tempResource.amount = Number(resource.amount) + 1 } return tempResource; }); 这具有相同的行为。这显然不是问题:/
    • Okey,请与我们分享一个可运行的示例,我可以帮助您
    • 我提供了一个 snip 代码,但是 - 它在示例中运行良好 - 当我将所有代码缩减为示例中的代码时 - 它仍然无法在我的机器上正确运行。 ..唯一的区别是它在多个文件上(我看到的唯一区别)。
    【解决方案4】:

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

      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 使用带有 prevState 的函数会在一个只调用一次的函数中导致两个函数调用......我已经读过我应该使用 prev state 作为 this.state.resources.slice ();只是拍摄“不定时快照”并且可能不可靠。这是真的……还是这种方法可以接受?

    这是对其他任何为此苦苦挣扎的人的答案。希望在对可能发生的事情有所启发之后,可以发布更好的答案。

    【讨论】:

      猜你喜欢
      • 2013-12-11
      • 2021-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多