【问题标题】:Why does adding to array of objects in state alters the other objects in that array ? (React)为什么添加到状态中的对象数组会改变该数组中的其他对象? (反应)
【发布时间】:2020-03-11 18:25:46
【问题描述】:

总的来说,我对 React/开发相对较新(大约 5 个月),我偶然发现了以下问题,这让我非常抓狂。我想知道我是否愚蠢并且错过了一个基本概念,或者这是更深层次的东西。

给你一些关于我的项目的背景以及这个问题是如何出现的:

我正在构建一个计算器应用程序,非常类似于您在 Google 上搜索“计算器”时获得的应用程序。我正在尝试编写一个函数,每次用户单击计算器上的按钮时,都会将与显示相关的所有内容保存为对象,并将该对象添加到保持状态的数组中,该数组的整个“输入历史”计算器。这样,当用户想逐个删除显示值时,计算器只需返回输入历史数组中的一个索引,并及时返回到之前的设置。这样计算器仍然可以在删除(指数、不匹配的括号等)时显示漂亮的动态渲染样式,而无需编写非常长、复杂且几乎可以肯定有错误的删除函数。

为了实现这一点,我告诉我的程序,在每次状态更改(我保留显示值的位置)后,将当前设置和显示添加到输入历史数组中。我选择将此函数放在 componentDidUpdate 方法中,并确保仅在 this.state.inputHistUpdated 为 false 时调用它来确保它只触发一次,每次将某些内容添加到显示时都会发生这种情况。

这是我的函数,在 componentDidUpdate() 方法中:

if (!this.state.inputHistUpdated) {
        let newInputHist = {
            currentMath : this.state.currentMath, 
            exponentCount : this.state.exponentCount, 
            spanStr : this.state.spanStr, 
            rightParensArr : this.state.rightParensArr, 
        }
        console.log(newInputHist);

        this.setState((prevState) => {

            return {
                inputHistory : [...prevState.inputHistory, newInputHist],
                inputHistUpdated : true
            }
        })

    }

输入对象中的每个值都可能受到用户输入内容的影响,并以某种形式出现在显示中。

问题是这样的

用户在计算器中输入一个 5。 newInputHist 记录单个对象,其 currentMath 值为 5。(这是预期的)。 React DevTools 还显示 this.state.inputHist 是一个对象的数组,其 currentMath 值也是 5(如预期的那样)。 这就是问题所在:假设用户在计算器中输入了“+”。 newInputHist 记录一个 currentMath 值为 '5 +' 的对象,这是正确的,并且已将一个新对象添加到 this.state.inputHist 中,其 currentMath 值也是 '5 +' 但另一个元素的 currenMath 值我们之前添加的那个数组也已从“5”更改为“5 +”。每次我更新显示时都会发生这种情况; this.state.inputHist 数组中每个对象的 currentMath 属性都会更新为 CURRENT currentMath 属性,而不是保存它最初拥有的属性。

这对我来说完全没有意义。我尝试过将对象连接到数组的其他方法,并在 Google 和 SO 上下搜索以寻找解决方案,但找不到任何有用的东西。

如果您需要更多信息/代码,请告诉我您认为可能发生的情况。我想给你足够的背景,等等,所以你可以回答我的问题而不添加任何无关的东西,但如果我不能随意这么说。

这里有更多代码:

此函数将数字/操作/所有内容添加到显示中

addToDisplay(input) {
    this.setState((prevState) => {
        let calcArr = prevState.newCalc ? [] : prevState.currentMath
        if (prevState.lastInput=== '□') {
            calcArr.pop(); 
        }

        if (prevState.lastInput === ' - ' && /[×\-÷\+]/.test(prevState.currentMath[prevState.currentMath.length-2])) {
            calcArr[calcArr.length-1] = ' -'

        }

        calcArr.push(input)
        return { 
            currentMath : calcArr,
            calcActive : true,
            lastInput : input,
            newCalc : false, 
            inputHistUpdated : false
        }
    });
}

这两个函数处理显示中动态呈现的 UI——当用户输入左括号时添加特殊样式的右括号,当用户在指数中上升一级时添加必要的不可见 </spans>,该指数具有不同的风格

  rightParensFunc() {

    let styledParens = `<span class='parens exp-${this.state.exponentCount}'>)</span>`; 

    this.setState((prevState) => {
        return {
            rightParensArr : [styledParens, ...prevState.rightParensArr], 
            inputHistUpdated : false 
        }
    });


}

spanFunc() {
    let counter = this.state.exponentCount; 
    let spans = ''; 
    while (counter > 0) {
        spans = spans + '</span>';
        counter--;
    }; 
    this.setState({
        spanStr : spans,
        inputHistUpdated : false
    })   
}

然后这是我的构造函数

class CalcBody extends React.Component {
constructor(props){
    super(props);
    this.state = {
        calcActive : false, 
        angleUnits : 'radians',
        historyVisible : false, 
        inverse : false,
        prevTotal : '0', 
        lastInput : '0',
        currentMath : [],
        exponentCount : 0,
        prevMath : '', 
        rightParensArr : [],
        spanStr : '',  
        calcHistory : [],
        inputHistory : [], 
        inputHistUpdated : true, 
        newCalc : true
    };

【问题讨论】:

  • 你能显示更多的代码吗?似乎所有处于状态的对象都在引用内存中的同一个对象,但我没有立即在给定的代码中看到罪魁祸首。 console.log(newinputHist) 的输出可能就足够了。
  • 抱歉,这里是新手——我如何向您显示该输出?你想看看我的开发工具中有什么吗?
  • 不用了,我最感兴趣的是看看currentMathrightParensArr的数据类型是什么,但你用新代码给了我。

标签: javascript arrays reactjs


【解决方案1】:

您的问题似乎在这里:

let newInputHist = {
  currentMath : this.state.currentMath, // Here
  exponentCount : this.state.exponentCount, 
  spanStr : this.state.spanStr, 
  rightParensArr : this.state.rightParensArr, // Here
}

JavaScript 数组和对象作为引用存储在变量中。您可以阅读有关in this answer 含义的更详细说明(在“说明”标题下)。

要解决这个问题,您需要像这样创建数组的真实副本:

let newInputHist = {
  currentMath : [...this.state.currentMath],
  exponentCount : this.state.exponentCount, 
  spanStr : this.state.spanStr, 
  rightParensArr : [...this.state.rightParensArr],
}

发生了什么

您在历史中的数组,以及用于存储当前数学的数组在内存中引用了同一个数组。意味着改变一个改变两者。

这是如何解决的

通过使用 spad 语法[...array],我们创建了一个全新的数组。不再有意外更改。

另外:在addToDisplay 函数中,出于与上述相同的原因,您意外地改变了状态。改为:

let calcArr = prevState.newCalc ? [] : [...prevState.currentMath]

【讨论】:

  • 你修好了!非常感谢。另外,感谢您非常详细的解释。以后避免这个问题会很有帮助!再次感谢。
【解决方案2】:

currentMath 是一个数组(对象),因此被同一个引用传递。

这就是您的应用中发生的情况:

Memory Location   |   Variable pointing at it

     xyz                   currentMath

当加 5 时:

let newInputHist = {
  currentMath: this.state.currentMath, 
  // others
}

// value of currentMath is 5
inputHistory = [
  { currentMath: <value points to xyz memory>, // others }
]

当你输入 + next 时,currentMath 变为 5+

inputHistory = [
  { currentMath: <value points to xyz in memory>, // others },
  { currentMath: <value points to xyz in memory>, // others }
]

所以基本上所有后续对象都有它们的currentMath 指向相同的内存位置。添加新数据后,它会更新,新值会反映在所有 currentMath 中。

为避免这种情况,您必须确保内存位置不同。

方法 1
您可以像这样获取浅拷贝:

let newInputHist = {
  currentMath : [ ...this.state.currentMath], 
  // keep others as usual
}

方法 2
或者像这样进行深拷贝:

let newInputHist = {
  currentMath : JSON.parse(JSON.stringify(this.state.currentMath)), 
  // keep others as usual
}

这些应该可以解决您的问题。我鼓励你阅读this up。

我还想在这里提到您希望采用方法 2 的场景。 假设 currentMath 中还有另一个对象:

currentMath = [ { key: value }, <some primtive like string> ]

在这种情况下,currentMath[0][...currentMath[0]] 将指向同一个内存位置,尽管 currentMath[1][...currentMath[1]] 将指向不同的内存位置。您可以使用深拷贝方法解决它。

【讨论】:

    猜你喜欢
    • 2019-06-16
    • 2020-01-10
    • 1970-01-01
    • 2017-06-05
    • 2021-05-09
    • 2019-10-02
    • 2019-01-10
    • 2020-11-07
    • 1970-01-01
    相关资源
    最近更新 更多