0%

已完成 0/29 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 18 关 含代码 约 8 分钟

更新 State 中的对象:不可变性与展开语法复制

更新 State 中的对象:不可变性与展开语法复制

在 React 中,State 可以存放任何类型的 JavaScript 值,包括数字、字符串、布尔值,也包括对象(Objects)。

但是,当你把一个对象放进 State 之后,你必须遵守一条极为严苛的原则: 永远将 State 中的对象视为只读的,切勿直接修改(变异 Mutation)原对象!


1. 什么是变异(Mutation)?为什么它在 React 里是禁忌?

在 JavaScript 中,技术上你可以随时这样写:

const [position, setPosition] = useState({ x: 0, y: 0 });

function handlePointerMove(e) {
  position.x = e.clientX; // 直接修改了内存中原对象的属性!
  position.y = e.clientY;
}

这种直接对已经存在的对象属性重新赋值的操作,被称为 变异(Mutation)。

为什么这样做在 React 里完全失效?

  1. React 依靠对象引用的改变来判断是否发生了更新: 在底层,React 使用 Object.is 比较新旧状态。如果直接变异了原对象,新对象和旧对象指向的依然是内存中的同一块地址(引用完全相同),React 就会以为“什么都没有变”,从而根本不会触发重新渲染!
  2. 破坏了快照与调试: 直接变异对象会污染过去渲染的所有历史快照,导致组件的时间旅行、性能优化(React.memo)和调试工具全部失灵。

正道做法:永远不要修改旧对象,而是创建一个全新的对象,并将新对象传给 set 函数!

function handlePointerMove(e) {
  setPosition({
    x: e.clientX,
    y: e.clientY
  });
}

2. 使用对象展开语法(…)复制并更新局部属性

通常情况下,一个对象里包含很多个属性(比如用户的姓名、邮箱、头像、年龄)。当用户在表单里修改了姓名时,你显然希望保留其他的属性不变。

如果每个属性都手动重新手敲一遍会非常繁琐:

setPerson({
  firstName: e.target.value,
  lastName: person.lastName,
  email: person.email
});

为了简化这个过程,我们使用现代 JavaScript 的 对象展开语法(Spread Syntax ...):

setPerson({
  ...person, // 复制现有 person 对象的所有属性
  firstName: e.target.value // 单独覆盖更新 firstName
});

语法说明: 属性的顺序非常关键!...person 必须放在最前面。写在后面的属性会覆盖前面复制过来的同名属性。


3. 处理嵌套对象的更新

如果对象内部还嵌套了另一个对象,比如:

const [person, setPerson] = useState({
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Paris',
    image: 'https://i.imgur.com/Sd1AgUOm.jpg',
  }
});

如果你想修改作品的城市 city: 注意:展开语法只是“浅拷贝(Shallow Copy)”! 如果只写 { ...person, artwork: { city: 'Tokyo' } },原本作品里的 title 和 image 就会全被丢弃!

要安全更新嵌套对象,必须层层展开拷贝:

setPerson({
  ...person, // 拷贝外层对象属性(保留 name)
  artwork: {
    ...person.artwork, // 拷贝内层 artwork 的所有属性(保留 title 和 image)
    city: 'Tokyo' // 仅覆盖更新 city
  }
});

总结

  • 将 React State 中的所有对象视为不可变(Immutable)的只读数据;
  • 永远不要直接 obj.x = 1,这不会触发重新渲染;
  • 使用对象展开语法 { ...obj, x: 1 } 生成一个属性被安全覆盖的新对象;
  • 更新嵌套对象时,需要由外到内逐层展开拷贝。
下一站:更新 State 中的数组:添加、删除、替换与排序 →
© 2026 React 官方教程