在React中使用setState更新对象

482

使用 setState 更新对象属性是否有可能?

例如:

this.state = {
   jasper: { name: 'jasper', age: 28 },
}

我已经尝试过:

this.setState({jasper.name: 'someOtherName'});

以及这个:

this.setState({jasper: {name: 'someothername'}})

第一个会导致语法错误,而第二个则什么也不做。有什么想法吗?


11
第二段代码本来可以工作,但是你会失去jasper对象中的age属性。 - Giorgi Moniava
1
我理解React使用.assign()方法将旧状态对象与新对象合并,所以第二段代码不应该正常工作吗? - Saurabh Rana
23个回答

1025
有多种方法可以实现这个,因为状态更新是一个异步操作,所以要更新状态对象,我们需要使用更新函数setState

1- 最简单的方法:

首先创建jasper的副本,然后对其进行更改:

this.setState(prevState => {
  let jasper = Object.assign({}, prevState.jasper);  // creating copy of state variable jasper
  jasper.name = 'someothername';                     // update the name property, assign a new value                 
  return { jasper };                                 // return new object jasper object
})

我们可以这样书写,而不是使用Object.assign

let jasper = { ...prevState.jasper };

2- 使用 扩展语法

this.setState(prevState => ({
    jasper: {                   // object that we want to update
        ...prevState.jasper,    // keep all other key-value pairs
        name: 'something'       // update the value of specific key
    }
}))

注意: Object.assignSpread Operator 只创建浅拷贝,因此如果您定义了嵌套对象或对象数组,则需要采用不同的方法。


更新嵌套状态对象:

假设您已经定义了状态如下:

this.state = {
  food: {
    sandwich: {
      capsicum: true,
      crackers: true,
      mayonnaise: true
    },
    pizza: {
      jalapeno: true,
      extraCheese: false
    }
  }
}

更新 pizza 对象的 extraCheese 属性:

this.setState(prevState => ({
  food: {
    ...prevState.food,           // copy all other key-value pairs of food object
    pizza: {                     // specific object of food object
      ...prevState.food.pizza,   // copy all pizza key-value pairs
      extraCheese: true          // update value of specific key
    }
  }
}))

更新对象数组:

假设你有一个待办事项应用程序,并且你是以以下形式管理数据的:

this.state = {
  todoItems: [
    {
      name: 'Learn React Basics',
      status: 'pending'
    }, {
      name: 'Check Codebase',
      status: 'pending'
    }
  ]
}

要更新任何待办事项对象的状态,请对数组运行映射,并检查每个对象的某个唯一值,如果condition=true,则返回具有更新值的新对象,否则返回相同的对象。
let key = 2;
this.setState(prevState => ({

  todoItems: prevState.todoItems.map(
    el => el.key === key? { ...el, status: 'done' }: el
  )

}))

建议:如果对象没有唯一值,则使用数组索引。


11
在你的第二个代码片段中,它会从jasper对象中删除其他属性。运行console.log(jasper),你会发现只有一个键nameage不再存在 :) - Mayank Shukla
1
@JohnSnow,如果jasper是一个object,那么这种方式是正确的,不管它是否最好,可能还有其他更好的解决方案 :) - Mayank Shukla
7
值得一提的是,Object.assign和展开运算符都无法进行深拷贝。因此,你应该使用像lodash的deepCopy等解决方案来解决这个问题。 - Alex Vasilev
1
这个 let { jasper } = this.state 怎么样? - Brian Le
1
我一直在苦恼嵌套对象的问题。感谢您清晰的解释。 - yetsun
显示剩余13条评论

78

这是最快和最易读的方式:

this.setState({...this.state.jasper, name: 'someothername'});

即使 this.state.jasper 已经包含了一个 name 属性,新的名字 name: 'someothername' 仍将被使用。


72
这个解决方案有一个很大的缺点:为了优化状态更新,React 可能会分组多个更新。因此 this.state.jasper 不一定包含最新的状态。最好使用带有 prevState 的符号表示。 - sinned

41

在这里使用展开运算符和一些ES6语法

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})

8
这会更新jasper,但是其他属性会从状态中删除。 - iaq

33

我知道这里有很多答案,但是让我惊讶的是没有一个在setState之外创建新对象的副本,然后简单地setState({newObject})。干净,简洁,可靠。所以在这种情况下:

const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))

或者对于动态属性(在表单中非常有用)

const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))


5
因为你使用了两个语句,而实际上只需要一个就可以表达清楚。 - Beau B.

27

使用钩子,我们可以按照以下方式完成

const [student, setStudent] = React.useState({name: 'jasper', age: 28});
 setStudent((prevState) => ({
          ...prevState,
          name: 'newName',
        }));

1
太好了!教程里肯定漏掉了这个。哈哈。 - drewkiimon
1
太完美了!在教程中肯定错过了这个。哈哈,抱歉抄袭了 ;) - Arvind K.

13

我使用了这个解决方案。

如果您有这样一个嵌套状态:

this.state = {
  formInputs:{
    friendName:{
      value:'',
      isValid:false,
      errorMsg:''
    },
    friendEmail:{
      value:'',
      isValid:false,
      errorMsg:''
    }
  }
}

你可以声明一个handleChange函数,它将复制当前状态并重新分配具有更改值的状态

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

这里是带有事件监听器的HTML代码。请确保使用与状态对象中相同的名称(在本例中为'friendName')

<input type="text" onChange={this.handleChange} " name="friendName" />

这对我来说有效,只是我不得不使用这个代替:statusCopy.formInputs[inputName] = inputValue; - MikeyE

10

试一下这个,应该能很好地工作

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));

9
您正在直接变异状态对象。要使用此方法,请将一个新对象添加为源: Object.assign({}, this.state.jasper, {name:'someOtherName'}) - Albizia

7
创建一个状态对象。
this.state = {
  objName: {
    propertyOne: "",
    propertyTwo: ""
  }
};

使用setState更新状态

this.setState(prevState => ({
  objName: {
    ...prevState.objName,
    propertyOne: "Updated Value",
    propertyTwo: "Updated value"
  }
}));

7
在函数组件中使用钩子:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
      setState(prevState => ({
            ...prevState,
            prevState.jasper.name = "Anurag",
            prevState.jasper.age = 28
      })
    )
}

在这些情况下,建议使用基于回调的方法来更新状态,因为使用这种方法可以确保先前的状态已完全更新,并且我们是基于先前更新的状态进行更新。

5

这是另一种解决方案,使用immer不可变工具,非常适用于处理深度嵌套的对象,并且您不需要关心变异。

this.setState(
    produce(draft => {
       draft.jasper.name = 'someothername'
    })
)

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