如何正确更新处于反应状态的对象或数组[重复]

Posted

技术标签:

【中文标题】如何正确更新处于反应状态的对象或数组[重复]【英文标题】:how to properly update an object or array in react state [duplicate] 【发布时间】:2019-11-08 05:24:28 【问题描述】:

假设这是我的状态:

state=
  user:
    name: 'Joe',
    condition:
      isPrivate: true,
      premium: false
    
  

这是我可以用来更新user的方法:

updateUser = (property, value)=>
  // firstway. probably not a good one
  let user = this.state.user; 
  user[property] = value;
  this.setState(user)

  // second way. probably the best way
  let user = JSON.parse(JSON.stringify(this.state.user))
  user[property] = value;
  this.setState(user)

虽然我知道直接修改状态不是一个好习惯,但我从他们两个得到的结果相同,到目前为止没有任何副作用。 那么为什么我要采取这个额外的步骤来复制状态然后在复制的对象上修改它,而这会减慢操作(但是很少)! 那么哪一个会更快呢?在反应的背景下,第一种方法的副作用是什么?最后每种方法的优缺点是什么?

【问题讨论】:

有一个概念叫不变性。值得一读。 @Zorig 我知道它是什么,但在 react 的上下文中,我想知道它会产生什么副作用 想象一下简单的场景:您有一个组件,您可以在其中定义在 2 秒内触发的回调和用户的 console.log 名称。在 1 秒内,你改变了状态内的用户名。会打印什么?以不可变的方式考虑数据要容易得多。 更好的方法:this.setState((user) => ( user: ...user, [property]: value )) 【参考方案1】:

基本思想是避免改变对象,而是创建新对象

这个口头禅意味着你应该避免直接改变内存中的javascript对象,而是每次都创建一个新对象。

您可以使用 ES6 spread operator 来获取对象的克隆。使用扩展运算符,您还可以更新克隆的属性,以便对对象属性执行所需的更新。

这是你需要的代码:

updateUser = (property, value) => 
  const user = ...this.state.user, [property]: value; // gets a clone of the current user and update its properties
  this.setState( user );

上面语法中的三个点不是错别字,它们是前面提到的 ES6 扩展运算符

根据我的知识(我对反应很陌生),基本上有三个原因可以避免直接状态突变:

每次重新计算新状态比尝试更新现有状态更简单。当我说更简单时,我的意思是从概念和编码的角度来看更简单。每次创建一个新对象避免任何副作用都会简化您的代码并减少您的错误。

您无法确定您的组件及其子组件如何使用给定的状态。该状态由您的组件使用,并且可以通过 props 传递给其子组件。如果您仅对组件进行单独推理,您将无法知道子组件如何使用该状态。当你通过改变它的属性来改变内存中的对象时会发生什么?响应是谁知道。您可能会产生一系列副作用,更重要的是,如果您仅对组件进行单独推理,您无法确定会得到什么样的副作用。这实际上取决于组件层次结构的组成方式。关于副作用的推理总是一团糟,处理它们的风险太大,更好的方法是试图避免它们。

react 和 react dom 旨在通过遵循函数式方法的最佳实践(无副作用和无直接状态突变)在状态发生突变时有效地更新浏览器 DOM。这意味着,如果您按照建议的方式使用 react,react 本身将有更好的时间来计算要应用于 DOM 的修改,以便重绘您的组件,然后您的应用会表现得更好。

【讨论】:

我知道三个点的作用,这不是我的答案,你只是用另一种方式用浅拷贝复制对象,我是深拷贝,所以condition 属性也被复制了。我只想知道react中第一种方法的副作用 @BlueTurtle 即使使用扩展运算符(如 Enrico 提到的),condition 属性也会被复制,对吗? @Sriraman 我不这么认为,传播运算符只是复制对象本身而不是嵌套对象,无论如何,这不是我的问题!我不想知道复制状态的正确方法,我想知道第一种方法可能产生的影响并比较两种方法的性能 @BlueTurtle 扩展运算符无法执行深层复制【参考方案2】:

作为对您更新状态的第一种方法的响应,您将获得对嵌套在您的状态中的对象的引用。

let user = this.state.user; 
user[property] = value;

在这个块中你已经更新了状态,所以你实际上是在执行一个副作用。对 setState() 的调用只是反映了 UI 中的这些变化(即重新渲染组件)。

不直接修改状态的原因可能是状态的一些无意更新。例如,如果您想通过修改 this.state 中的一些数据并将其作为请求的主体发送来进行 api 调用(请注意,您不希望这些更新反映在 UI 中),然后修改像在方法 1 中所做的那样直接使用 state 可能会导致状态发生一些不需要的更改,随后对 setState() 的调用可能会将一些不需要的更改暴露给应用程序的用户。

但是在您的示例中,使用其中任何一种方法都可以,但这可能不是一个好习惯。

希望这会有所帮助!

【讨论】:

以上是关于如何正确更新处于反应状态的对象或数组[重复]的主要内容,如果未能解决你的问题,请参考以下文章

反应挂钩以根据先前的状态值更新状态[重复]

推送到数组时反应状态无法正确更新

更新反应状态而不覆盖先前的状态

反应上下文对象似乎没有再次正确更新[重复]

反应:组件状态不更新记分员或计数器

如何正确删除操作?