
在 React 中直接对 state 的嵌套数组或对象进行修改(如赋值、属性变更)会导致原始 state 被意外更改,根本原因是仅用展开运算符 [...arr] 仅实现一层浅拷贝,内部对象仍共享引用。
在 react 中直接对 state 的嵌套数组或对象进行修改(如赋值、属性变更)会导致原始 state 被意外更改,根本原因是仅用展开运算符 `[...arr]` 仅实现**一层浅拷贝**,内部对象仍共享引用。
当你执行:
let mappingConfig = [...formData.mappedColumns];
你确实创建了一个新数组,但该数组中的每个元素(即 { source, target, transformationValue } 对象)仍是原对象的同一引用。因此后续操作:
mappingConfig[mIndex].source = 'source'; mappingConfig[mIndex].transformationValue = 'value';
实际是在直接修改原始 formData.mappedColumns[mIndex] 所指向的内存对象——这违反了 React 的不可变更新原则,导致 UI 不同步、状态不一致甚至难以调试的副作用。
✅ 正确做法是:对嵌套对象也执行结构化深拷贝(至少到需修改的层级)。推荐使用 map + 展开语法,为每个对象生成独立副本:
const mappingConfig = formData.mappedColumns.map(item => ({ ...item }));
const mIndex = mappingConfig.findIndex(p => p.target == targetIndex);
if (mIndex !== -1) {
// 创建新对象,不修改原引用
mappingConfig[mIndex] = {
...mappingConfig[mIndex],
source: 'source',
transformationValue: 'value'
};
}
// 最后通过 setFormData 安全更新状态
setFormData(prev => ({
...prev,
mappedColumns: mappingConfig
}));
⚠️ 注意事项:
- ❌ 避免
JSON.parse(JSON.stringify()):虽可深拷贝,但会丢失函数、Date、undefined、Symbol 等类型,且性能差; - ✅ 优先使用逻辑清晰的解构复制(如
{ ...obj })或structuredClone()(现代浏览器支持); - ? 若嵌套更深(如
mappedColumns[i].rules[j].config),需逐层展开,或使用工具库(如 Lodash 的cloneDeep); - ? 更新 state 务必使用函数式更新(
setFormData(prev => {...})),确保基于最新状态,避免闭包 stale props 问题。
总结:React state 更新必须保证引用不可变。浅拷贝数组 ≠ 浅拷贝其元素;任何对嵌套对象的修改,都需先创建该对象的新副本,再更新并替换整个结构。这是函数式 UI 编程的核心纪律之一。











