React中useState在组件重渲染时不更新值:点击改名按钮后UI未同步刷新
问题现象
- 点击改名按钮时状态变更逻辑确实正常触发,但UI未同步渲染更新后的内容,预期正确效果是点击按钮后名称修改为 "Mike"
- 附带可复现的代码沙盒示例,核心示例代码如下:
import React, { useState } from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const useForm = initialValues => { const [values, setValues] = useState(initialValues); return [ values, e => { console.log(e.target.value); setValues({ ...values, [e.target.name]: e.target.value }); } ] } let count = 0; const Hello = (props) => { console.log(`Hello.props: ${props.name} `) const [values, handleChange] = useForm({ nameInput: props.name }); count++ console.log(`RenderedAmount: ${values.nameInput} : ${count}`) return ( <div> Name: <input value={values.nameInput} onChange={handleChange} name="nameInput" /><br/> Hello {values.nameInput}! </div> ); } const Container = (props) => { const [ name, setName] = useState(props.name); console.log(`Container: Name is ${name}`) return( <div> <Hello name={name}/> <button onClick={()=>{setName("Mike")}}> Change Name </button> </div> ) } const rootElement = document.getElementById("root"); ReactDOM.render(<Container name="John"/>, rootElement);
问题产生原因
- React 中
useState的初始参数仅会在组件首次挂载渲染时被读取一次,后续组件接收的props发生变化时,不会自动重新执行初始化逻辑更新内部状态。 - 上述代码中,Hello组件通过自定义
useFormHook初始化表单状态时,传入useState的初始值{ nameInput: props.name }只会在Hello组件第一次挂载时生效,将nameInput赋值为初始传入的"John"。 - 当父组件Container中的name状态通过点击按钮更新为"Mike"时,Hello组件接收到的
props.name确实发生了变化,但Hello内部维护的表单状态values是独立于props存在的,没有同步最新的props值;而UI渲染时Hello读取的是自身存储的values.nameInput,因此无法展示出最新的名称值。 - 从控制台输出日志也可以验证这一逻辑:点击按钮后Container组件会打印最新的name值为"Mike",但Hello组件内部读取的
values.nameInput始终是首次初始化的"John",因此渲染结果没有变化。 - 额外的隐患:
useForm里的表单更新逻辑直接依赖闭包捕获的values做状态合并,一旦存在异步触发的状态更新,很容易出现旧值覆盖新值的问题。
修复方案
- 方案一:通过key值强制组件重挂载。在Container渲染Hello组件时,将当前name值作为key绑定到Hello上,当name值变化时React会自动卸载旧的Hello实例、挂载新的Hello实例,新实例初始化表单状态时就会读取到最新的props.name值,示例写法:
<Hello key={name} name={name}/> - 方案二:监听props变化同步内部状态。首先修改
useFormHook的返回结构,将内部维护的setValues方法暴露给外部,同时把状态更新逻辑改为函数式写法避免闭包问题;然后在Hello组件中通过useEffect监听props.name的变化,一旦检测到props值更新,就同步修改内部存储的nameInput值,示例代码:// 重构useForm,返回状态更新方法并优化闭包问题 const useForm = initialValues => { const [values, setValues] = useState(initialValues); return [ values, e => { setValues(prev => ({ ...prev, [e.target.name]: e.target.value })) }, setValues ] } // Hello组件内部新增props同步逻辑 const Hello = (props) => { const [values, handleChange, setValues] = useForm({ nameInput: props.name }); // 监听props变化同步内部表单状态 useEffect(() => { setValues(prev => ({ ...prev, nameInput: props.name })) }, [props.name, setValues]) // 剩余渲染逻辑保持不变 } - 方案三:状态提升。如果不需要Hello组件维护独立的表单状态,可将表单的输入状态提升到父组件Container中统一管理,Hello组件改为完全受控组件,直接读取父组件传入的props做渲染,状态变化时UI自然会同步更新。
内容的提问来源于stack exchange,提问作者Shivam Sinha
相关产品推荐
相关产品推荐

