React中按索引删除useState数组元素及解决defaultValue更新问题
解决React中useState数组删除元素后Input defaultValue不更新的问题
问题场景
我通过useState维护学生列表数组:
const [students, setStudents] = useState([""])
将数组映射为Student组件:
{students.map((student, index) => ( <Student setStudents={setStudents} students={students} id={index} key={index} content={student} /> ))}
AddStudent组件可以正常添加新学生:
function AddStudent(props) { const {setStudents} = props; return ( <button className="change-student add-student" onClick={() => { setStudents((students) => [...students, ""]) }}> + </button> ); }
但RemoveStudent组件按索引删除元素时始终无法正常工作,尝试了多种写法都无效:
function RemoveStudent(props) { const {students, setStudents, id} = props; return ( <button className="change-student remove-student" onClick={() => { let data = students; if(id > -1) { data.splice(id, 1); } console.log(data) // setStudents(data) // alternative: // setStudents(students.filter(index => index !== id)); // removes the last element in the list // doesn't work properly }}> - </button> ) }
问题分析
- 直接修改原数组:
splice会直接篡改原students数组的引用,React依赖状态引用变化触发渲染,因此无法检测到状态更新。 - Filter逻辑错误:
students.filter(index => index !== id)中,filter的第一个参数是数组元素而非索引,这段代码实际是删除值等于id的元素,而非对应索引的元素,导致删除结果不符合预期。 - Key复用导致的渲染异常:使用数组索引作为
Student组件的key,删除元素后后续组件的key会前移复用,React会认为这些组件没有变化,不会重新渲染Student内部的Input组件——而defaultValue仅在组件挂载时生效,因此旧的defaultValue会被保留。
解决方案
1. 修正RemoveStudent的删除逻辑
采用不可变更新方式,基于前一次状态创建新数组,同时正确使用filter按索引删除:
function RemoveStudent(props) { const {setStudents, id} = props; return ( <button className="change-student remove-student" onClick={() => { setStudents(prevStudents => prevStudents.filter((_, idx) => idx !== id)); }}> - </button> ) }
这里通过prevStudents获取最新状态,避免闭包导致的状态过期问题,同时利用filter的第二个参数(索引)精准删除目标元素。
2. 刷新Input的defaultValue
由于defaultValue仅在组件首次挂载时赋值,要让它随状态更新,需要让React重新创建Input组件。核心方法是给Input设置唯一的key,确保元素变化时key也随之变化:
// Student组件内的Input示例 function Student({ content, id }) { return ( <input defaultValue={content} key={`student-input-${id}-${content}`} // 其他属性 /> ); }
进阶优化:使用唯一ID代替索引作为Key(推荐)
给每个学生分配唯一ID,彻底避免索引作为Key带来的渲染问题:
// 初始化状态为包含唯一ID的对象数组 const [students, setStudents] = useState([{ id: Date.now(), content: "" }]); // 映射组件时使用学生ID作为Key {students.map((student) => ( <Student setStudents={setStudents} students={students} id={student.id} key={student.id} content={student.content} /> ))} // 修改AddStudent组件,添加带唯一ID的学生对象 function AddStudent(props) { const {setStudents} = props; return ( <button className="change-student add-student" onClick={() => { setStudents((prev) => [...prev, { id: Date.now(), content: "" }]); }}> + </button> ); } // 修改RemoveStudent组件,按学生ID删除 function RemoveStudent(props) { const {setStudents, id} = props; return ( <button className="change-student remove-student" onClick={() => { setStudents(prevStudents => prevStudents.filter(student => student.id !== id)); }}> - </button> ) }
核心原理
当React组件的key发生变化时,会销毁旧组件实例并创建新的组件实例,此时Input的defaultValue会重新读取最新的状态值,从而实现内容更新。
内容的提问来源于stack exchange,提问作者ahaMfM
相关产品推荐
相关产品推荐

