React子组件调用父组件函数无法获取更新的父组件状态
问题:React多步骤登录中子组件提交无法获取更新后的父组件状态
问题现象
实现自定义多步骤登录功能时,父组件向子组件传递状态钩子和表单提交函数,出现以下异常:
- 父组件内的按钮调用提交函数时,能正确获取更新后的
input状态 - 子组件表单提交时,提交函数内的
input状态始终为空 useEffect能正常打印用户输入时更新后的input值
代码示例
父组件代码
export default function() { const [input, setInput] = useState({ email: "", password: "" }); function handleSubmitEvent(e) { e.preventDefault(); console.log("handleSubmitEvent input",input) return; }; useEffect(() => { console.log("useEffect input ", input) /*用户输入时能正确显示更新后的input值*/ }); const [ loginSteps, setLoginSteps] = useState( { steps:[ <CheckRegistry input={input} setInput={setInput} handleSubmitEvent={handleSubmitEvent}/>, <CheckPassword/> ], stepIndex:0 }) return ( <div> <button onClick={handleSubmitEvent}></button> {/* 点击父组件内按钮时,handleSubmitEvent能正确获取更新后的input状态 */} {loginSteps.steps[loginSteps.stepIndex]} </div> ) }
子组件代码
export default function(props) { const handleInput = (e) => { const { name, value } = e.target; props.setInput((prev) => ({ ...prev, [name]: value, })); }; return ( <> <form onSubmit={(e) => {props.handleSubmitEvent(e)}}> <input className="email-input" type="email" name="email" onChange={handleInput} /> <button></button> {/* 点击子组件内按钮时,handleSubmitEvent中的input状态为空 */} </form> </> ) }
问题原因
这是React闭包陷阱导致的:
- 父组件初始化
loginSteps状态时,直接将<CheckRegistry>组件实例存入steps数组。此时handleSubmitEvent函数捕获的是初始状态下的空input对象。 - 后续用户输入更新
input时,父组件会重新渲染,但loginSteps的状态未被更新,子组件仍然是最初创建的实例,它接收的handleSubmitEvent还是旧的函数,里面的input始终锁定为初始值。 - 父组件内的按钮是在每次渲染时绑定最新的
handleSubmitEvent,因此能获取到最新的input状态。
正确实现方式
不要将组件实例存入useState,而是在渲染阶段根据stepIndex动态渲染对应组件,这样每次父组件渲染时都会生成新的组件实例,传递最新的props(包括最新的handleSubmitEvent)。
修改后的父组件代码:
export default function() { const [input, setInput] = useState({ email: "", password: "" }); // 单独维护步骤索引,无需存储组件实例 const [stepIndex, setStepIndex] = useState(0); function handleSubmitEvent(e) { e.preventDefault(); console.log("handleSubmitEvent input",input) return; }; useEffect(() => { console.log("useEffect input ", input) }); // 根据步骤索引动态渲染对应组件 const renderCurrentStep = () => { switch(stepIndex) { case 0: return <CheckRegistry input={input} setInput={setInput} handleSubmitEvent={handleSubmitEvent}/>; case 1: return <CheckPassword/>; default: return null; } } return ( <div> <button onClick={handleSubmitEvent}></button> {renderCurrentStep()} </div> ) }
额外优化(可选)
如果需要避免handleSubmitEvent每次渲染生成新函数,可以用useCallback包裹:
const handleSubmitEvent = useCallback((e) => { e.preventDefault(); console.log("handleSubmitEvent input",input); }, [input]); // 依赖input,input更新时生成新函数
内容的提问来源于stack exchange,提问作者jigzat
相关产品推荐
相关产品推荐

