跳到主要内容

更新状态对象

React 的状态可以存储任何类型的 JavaScript 值,包括对象。但是,你不应该直接修改存储在 React 状态中的对象。相反,当你想要更新一个对象时,你需要创建一个新的对象(或者复制现有对象),然后将状态设置为使用这个新的副本。这种做法有助于保持状态的不可变性,从而确保 React 能够正确地追踪状态变化并触发必要的重新渲染。

状态是只读的

在 React 中,状态是只读的。这意味着你不能直接修改状态。相反,你必须通过状态更新函数来修改状态。状态更新函数是 React 提供的函数,用于更新组件的状态。

const [person, setPerson] = useState({ name: 'John', age: 30 });

// 错误的做法
person.age = 31;

// 正确的做法
setPerson({ ...person, age: 31 });
注意

展开运算符仅仅是浅拷贝,如果对象中包含嵌套对象,那么这些嵌套对象是不会被拷贝的。

复杂嵌套对象的更新

对于嵌套较深的对象,建议使用 immer 库来更新状态。

import { useImmer } from 'use-immer';

const [person, setPerson] = useImmer({ name: 'John', age: 30, address: { city: 'New York', country: 'USA' } });

setPerson(draft => {
draft.age = 31;
draft.address.city = 'London';
});

immer 里面的 draft 是一个代理对象,它可以帮助我们避免直接修改状态,从而确保状态的不可变性。

为什么React不直接修改状态?

React不直接修改状态有以下几个原因:

  1. 调试便利:如果使用console.log且不修改状态,之前的日志不会被最新的状态变化覆盖。这样可以清晰地看到状态在渲染之间的变化。

  2. 性能优化:React常见的优化策略依赖于在前后props或state相同时跳过工作。如果从不修改状态,检查是否有变化会非常快速。如果prevObj === obj,可以确定其内部没有任何变化。

  3. 新特性支持:新React特性依赖于将状态视为快照。如果修改过去版本的状态,可能会阻碍使用这些新特性。

  4. 需求变更:某些应用功能(如实现撤销/重做、显示变更历史或允许用户将表单重置为早期值)在不进行修改时更容易实现。这是因为可以在内存中保留状态的过去副本,并在适当时重用它们。如果一开始采用可变方法,后续添加这些功能可能会变得困难。

  5. 简化实现:由于React不依赖于修改,它不需要对对象进行特殊处理。不需要劫持属性、始终将它们包装到代理中,或在初始化时执行其他许多"响应式"解决方案所需的工作。这也是为什么React允许将任何对象(无论多大)放入状态中,而不会产生额外的性能或正确性问题。

回顾

  1. 状态是只读的,不能直接修改状态。
  2. 如果需要修改状态,可以通过状态更新函数来修改状态。
  3. 对于嵌套较深的对象,建议使用 immer 库来更新状态。