React useState管理对象数组时子组件修改值无法同步到父组件
问题排查
你的代码存在4个直接导致数据不同步的问题:
- 渲染列表时出现笔误:写的是
setTodos.map(),setTodos是修改状态的函数,不是存储todo列表的状态变量,正确的遍历对象应该是todos,这处错误会直接导致列表渲染逻辑异常。 - 子组件本地状态初始化错误:
CompoTwo内部维护的a/b/c三个输入状态初始值设为空字符串,没有绑定父组件传入的todo对应字段,初始渲染就和父组件数据不同步。 - 父组件状态更新逻辑缺失:子组件失焦触发的
handleChange方法没有写完,没有把输入框的修改同步回父组件的todos状态,父组件存的一直是新增时的默认值。 - 语法规范问题:子组件名写为小写开头的
compoTwo,React会将小写开头的标签识别为原生DOM节点,直接抛渲染错误;列表用数组索引作为key,增删改时容易出现状态错位。
修复步骤
- 修正列表遍历的笔误,将
setTodos.map改为todos.map - 新增todo时生成唯一id作为数据标识,列表渲染时用id作为key,替代数组索引
- 子组件名改为大写开头的
CompoTwo,符合React组件命名规范 - 子组件初始化本地状态时,直接读取父组件传入的
todo字段值,保证初始数据一致 - 补全子组件的状态更新逻辑,通过todo的唯一id定位要修改的条目,用不可变更新的方式修改父组件的todos数组
- 新增todo时用函数式更新,避免连续点击触发新增时的闭包旧值问题
修复后完整代码
const CompoOne = () =>{ const [todos, setTodos] = useState([]) const [subject, setSubject] = useState("") const [priority, setPriority] = useState("") const addTodo = () => { // 函数式更新规避闭包旧值问题 setTodos(prevTodos => [ ...prevTodos, // 生成唯一id用于后续更新定位 { id: `${Date.now()}-${Math.random().toString(36).slice(2,6)}`, a: "Title", b: "subTitle", c: "content" } ]) } return ( <> <InputText value={subject} setValue={setSubject} label="subject field" /> <InputText value={priority} setValue={setPriority} label="priority field" /> <hr /> <h2>Todo:</h2> <hr /> <button onClick={addTodo}> add a todo </button> <> {/* 修正遍历对象为todos */} {todos.map((el)=>( <CompoTwo todo={el} setTodos={setTodos} key={el.id} // 用唯一id作为key /> ))} </> <button onClick={()=>console.log(todos)}> show all objects </button> </> ) } // 组件名改为大写开头 const CompoTwo = ({ todo, setTodos }) =>{ // 初始化时直接绑定父组件传入的todo字段值 const [a, setA] = useState(todo.a) const [b, setB] = useState(todo.b) const [c, setC] = useState(todo.c) const handleChange = (e) =>{ const {name, value} = e.target setTodos((prevState)=>{ // 遍历定位当前修改的todo项,返回全新数组保证不可变更新 return prevState.map(item => { if(item.id === todo.id) { return { ...item, [name]: value } } return item }) }) } return( <> <InputText value={a} name="a" onChange={(e)=>setA(e.target.value)} onBlur={handleChange} label="field A" /> <InputText value={b} name="b" onChange={(e)=>setB(e.target.value)} onBlur={handleChange} label="field B" /> <InputText value={c} name="c" onChange={(e)=>setC(e.target.value)} onBlur={handleChange} label="field C" /> </> ) }
效果说明
修复后,新增待办项、修改子组件输入框内容触发失焦后,点击「show all objects」按钮,控制台会打印最新修改后的全量todos数据,不会再停留在新增时的默认初始值。
内容的提问来源于stack exchange,提问作者Farid212
相关产品推荐
相关产品推荐

