在React中更新对象状态的正确方法(合并状态? Spread运算符?)

4

我已经使用React有一段时间了,我一直使用扩展运算符来更新对象的状态,因为我记得阅读过应该直接更改当前状态。

例如,现在我会像这样写:

const changeState = () => {
  setState(prevState => {...prevState, existingKey: 'new value'})
} 

我刚刚在React文档中看到了一个名为"State Updates are Merged"的章节(https://reactjs.org/docs/state-and-lifecycle.html#state-updates-are-merged)。如果我理解得正确,它基本上是说我可以用以下代码代替原有代码:

const changeState = () => {
  setState({existingKey: 'new value'})
} 

我需要更新状态中指定的键,而对象中的所有其他键将保持不变。我理解得对吗?我在stackoverflow上从未见过这种写法,我总是看到人们建议使用展开运算符,这是有原因的吗?

3个回答

6
函数式状态更新和浅拷贝之所以被推荐,是因为它适用于所有的使用情况。您参考的state-updates-are-merged链接实际上适用于基于类的组件的this.setState方法。
  • Enqueueing multiple updates within a single render cycle, loops, etc...

    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    

    The net result here is only a single count + 1 update as each update overwrote the previous one.

    this.setState(prevState => ({ count: prevState.count + 1 }));
    this.setState(prevState => ({ count: prevState.count + 1 }));
    this.setState(prevState => ({ count: prevState.count + 1 }));
    

    The net result here is now count + 3.

  • Referencing the previous state in callbacks to avoid stale enclosures.

     componentDidMount() {
       setInterval(() => this.setState({ count:  this.state.count + 1 }), 1000);
     }
    

    Here the initial count state is closed over in callback scope and will never update.

     componentDidMount() {
       setInterval(() => this.setState(prevState => { count:  prevState.count + 1 }), 1000);
     }
    
  • Only the root properties are merged, more deeply nested state being updated also needs to be shallow copied.

     this.setState(prevState => ({
       ...prevState,
       property: {
         ...prevState.property,
         nestedProperty: 'new value',
       },
     }));
    
  • On the off-hand chance you are using the useState hook, state updates are not shallowly merged.

    useState

    Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

    const [state, setState] = useState({});
    setState(prevState => {
      // Object.assign would also work
      return {...prevState, ...updatedValues};
    });
    

一个好的经验法则

如果下一个状态以任何方式依赖于前一个状态,则使用函数式状态更新。否则,常规更新就足够了。


1
对于函数式组件,语法将如下所示:

const changeState = () => {
  setState(prevState => {...prevState, existingKey: 'new value'})
} 

对于类组件,语法如下:

const changeState = () => {
  // Notice the use of this
  this.setState({existingKey: 'new value'})
} 

-1
//Use react hooks for the functional component

import React, { useState, useEffect } from 'react';

//Suppose a user object with name, age, gender properties.

let [userData, setUserData] = useState({
    name: "Somnath Jana",
    age:27
    gender: "Male"
})

useEffect(()=>{
   userData.age = 28
   setUserData({...userData})
},[])

2
请注意,userData.age = 28 会改变状态对象。 - Drew Reese
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community
是的,我认为你不应该这样做,而是应该在更改年龄属性之前创建userData的副本。 - codego
这是错误的。正如@DrewReese所述,这将导致状态突变,从而引起微妙的错误。 - Dario Coletto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接