React函数组件中导入对象为何发生突变?
我正在学习React,遇到了不符合预期的行为:切换界面时试图把formFields状态重置为默认值,但之前通过FormUI的onChange设置的值依然保留,这说明底层的Template对象被突变了。不确定代码里哪部分导致了这个问题,想知道为什么onChange的数据会和Template产生交互。
我的代码
import { Box, Button, Grid, ThemeProvider, createTheme } from '@mui/material'; import './styling/App.css'; import RawPayloadUI from './components/RawPayloadUI'; import FormUI from './components/FormUI'; import { useState } from 'react'; import { Template } from './TemplateExporter'; function App() { const testFormFields = {...Template}; const [currentUI, setCurrentUI] = useState('rawPayloadUI'); const [formFields, setFormFields] = useState(testFormFields); const userInterfaces = ['rawPayloadUI', 'formUI']; const uiButtonHandler = e => { setFormFields(Template); //console.log(formFields); setCurrentUI(e.currentTarget.value) } return ( <ThemeProvider theme={theme}> <div className="App"> <div className='App-header'></div> <Grid id='Form' container sx={{ flexDirection: 'row', height: 'fit-content', marginTop: '1vh', justifyContent: 'center' }} spacing={2}> {userInterfaces.map((userInterface, idx) => { return ( <Grid item key={idx}> <Button value={userInterface} onClick={uiButtonHandler}>{userInterface}</Button> </Grid> ) })} </Grid> {currentUI === 'formUI' && <FormUI onChange={setFormFields} formFields={formFields} />} {currentUI === 'rawPayloadUI' && <RawPayloadUI />} </div> </ThemeProvider> ); } export default App;
问题原因分析
浅拷贝导致引用共享:你用
const testFormFields = {...Template};做的是浅拷贝,如果Template是嵌套对象(包含内层对象或数组),浅拷贝只会复制外层对象的引用,内层的对象/数组依然和原Template共享内存地址。当FormUI的onChange修改formFields的嵌套属性时,实际上直接修改了原Template的内层数据,导致Template被意外突变。重置时使用已突变的Template:在
uiButtonHandler中你调用setFormFields(Template),但此时Template已经被之前的修改操作改变了,所以设置的状态并不是初始默认值,而是被修改后的版本,自然无法达到重置效果。
解决方法
1. 对Template进行深拷贝
创建状态时,必须对Template做深拷贝,确保状态对象和原Template完全隔离。如果Template不包含函数、Symbol等JSON无法序列化的类型,可以用JSON.parse(JSON.stringify(Template))实现深拷贝:
// 初始化状态时直接深拷贝 const [formFields, setFormFields] = useState(JSON.parse(JSON.stringify(Template)));
如果Template包含特殊类型,可以使用lodash的cloneDeep方法,或者自己实现递归深拷贝函数。
2. 保存干净的初始副本
提前在组件顶部保存一份深拷贝后的初始模板,重置时直接使用这份干净的副本:
// 组件顶部定义干净的初始模板副本 const initialFormFields = JSON.parse(JSON.stringify(Template)); function App() { const [currentUI, setCurrentUI] = useState('rawPayloadUI'); // 用干净的副本初始化状态 const [formFields, setFormFields] = useState(initialFormFields); const uiButtonHandler = e => { // 重置到初始状态 setFormFields(JSON.parse(JSON.stringify(Template))); setCurrentUI(e.currentTarget.value); } // ... 其余代码不变 }
3. 确保FormUI组件不直接突变状态
检查FormUI组件的onChange实现,确保修改状态时是创建新对象,而不是直接修改传入的formFields。比如使用扩展运算符处理嵌套更新:
// 示例:FormUI中修改嵌套属性的正确方式 const handleFieldChange = (fieldPath, value) => { onChange(prev => ({ ...prev, [fieldPath]: { ...prev[fieldPath], value: value } })); };
或者使用immer库简化不可变状态更新,避免手动扩展对象。
内容的提问来源于stack exchange,提问作者Diagz

