React父组件重渲染后子组件state丢失问题排查
问题核心原因
你遇到的子组件state重置问题,根因是将子组件MyChildCmp定义在了父组件ParentComp的函数体内部:
- 父组件每次因
companyIcon状态更新触发重渲染时,都会重新执行函数体内的全部代码,此时会生成一个引用完全全新的MyChildCmp组件,和上一轮渲染的子组件不是同一个。 - React的diff逻辑判定组件类型的依据是组件引用,引用变化时会直接卸载旧组件、挂载全新的组件,旧组件内部存储的所有state(包括
myAddress)会被全部清空,回到初始值。
这个问题和你写的useCallback缓存回调没有关系,只要子组件是嵌套定义在父组件内的,不管你怎么缓存props,都避免不了子组件被卸载重建的问题。
另外你的代码还存在几个会直接导致运行报错的隐性问题:
- 子组件内引用的
myAddressExceptions、validateInputValue、handleBlur、mergedErrorMessages既没有在组件内部定义,也没有从props传入,实际运行会直接抛出引用错误。 - 父组件JSX中直接渲染对象类型的
companyIcon,页面上只会显示[object Object],不会展示你预期的状态内容。 - 子组件的输入框onChange逻辑里没有调用
setmyAddress同步输入值,输入框内容本身就无法正常更新。
修复方法
- 将
MyChildCmp的定义挪到ParentComp组件外部,禁止在组件函数体内嵌套定义其他组件。如果子组件需要依赖父组件的方法/值,全部通过props传递。 - 补全子组件内缺失的状态、方法定义,比如为
myAddressExceptions声明对应的state,补全校验、失焦处理逻辑,在输入框change事件里同步更新myAddress的值。 - 调整父组件的渲染逻辑,不要直接渲染对象类型的state,取对象内的具体字段展示即可。
修复后的核心参考代码:
// 子组件挪到父组件外部定义 function MyChildCmp(props) { const [myAddress, setmyAddress] = useState(''); // 补全缺失的异常状态定义 const [myAddressExceptions, setMyAddressExceptions] = useState([]); const mergedErrorMessages = (exceptions) => exceptions.map(i => i.message).join(','); const validateInputValue = (value) => { setmyAddress(value); // 自定义校验逻辑 const errors = []; if(!value) errors.push({type: 'error', message: '地址不能为空'}); setMyAddressExceptions(errors); } const handleBlur = () => { // 自定义失焦逻辑 } useEffect(() => { if (myAddressExceptions.length > 0) { props.validationCallback('MyInfo', true); } else { props.validationCallback('MyInfo', false); } }, [myAddressExceptions, props.validationCallback]) const handlemyAddressChange = (event) => { setmyAddress(event.target.value); console.log(myAddressExceptions); } return ( <> <div className="row" style={{ display: 'flex', flexDirection: 'row', width: '1000px'}}> <div style={{ width: '20%'}}> <FormField label='Company Address' required helperText={mergedErrorMessages(myAddressExceptions)} validationState={ myAddressExceptions[0] ? myAddressExceptions[0].type : '' } > <Input id='myAddress' value={myAddress} onChange={({ target: { value } }) => { validateInputValue(value); }} onBlur={handleBlur} inputProps={{maxLength: 9}} /> </FormField> </div> </div> </> ); } function ParentComp(props) { const [companyIcon, setCompanyIcon] = useState({ name: "icon", value: '' }); const [displayMyChild, setDisplayMyChild] = useState(true); // 补全缺失的displayMyChild定义 const validationCallback = useCallback((tabId, hasError) => { if (hasError) { setCompanyIcon(prevItem => ({ ...prevItem, value: 'error'})); } else { setCompanyIcon(prevItem => ({ ...prevItem, value: ''})); } }, []); return ( <div className="mainBlock"> {/* 不要直接渲染对象,取具体字段渲染 */} Parent : {companyIcon.value} {displayMyChild && <MyChildCmp validationCallback={validationCallback}/>} </div> ) } export default withRouter(ParentComp);
内容的提问来源于stack exchange,提问作者copenndthagen
相关产品推荐
相关产品推荐

