React问卷应用移除问题后输入值错位问题的解决方法
问题原因
- 错误将组件实例存入state:你在
drafts数组里直接存储<MultipleChoiceDraft />组件实例,这违反了React状态管理的核心原则——state应该存储数据而非UI组件。React无法正确追踪这些预创建组件的生命周期,删除操作后组件实例未被正确卸载,导致旧输入内容残留。 - 使用index作为key:在
drafts.map中用循环index作为列表项的key,删除中间元素后,后续元素的index会发生变化,React的Diff算法会复用原有组件而非重新渲染,导致旧状态被保留在错误位置。
修复方案
1. 重构状态存储:存数据而非组件实例
修改QuestionsDraft的drafts状态,只存储每个问题的纯数据结构(id、问题文本、选项、答案等),不再存储组件实例。
2. 让子组件成为受控组件
将MultipleChoiceDraft的内部状态(question、choices、answer)提升到父组件统一管理,通过props传递数据和更新回调,确保每个问题的状态与对应数据绑定。
3. 使用唯一id作为key
用每个问题的唯一id作为列表项的key,而非循环index,确保React能准确识别每个组件实例。
完整修复代码
QuestionsDraft.js
const QuestionsDraft = () => { // 存储问题数据,而非组件实例 const [drafts, setDrafts] = useState([ { id: 0, question: "", answer: null, choices: {0: "", 1: "", 2: "", 3: ""} }, { id: 1, question: "", answer: null, choices: {0: "", 1: "", 2: "", 3: ""} } ]) const remove = (questionId) => { setDrafts(prev => prev.filter(draft => draft.id !== questionId)) } // 更新问题文本的回调 const updateQuestion = (questionId, newText) => { setDrafts(prev => prev.map(draft => draft.id === questionId ? {...draft, question: newText} : draft )) } // 更新选项的回调 const updateChoice = (questionId, choiceIndex, newText) => { setDrafts(prev => prev.map(draft => { if (draft.id === questionId) { const newChoices = {...draft.choices} newChoices[choiceIndex] = newText return {...draft, choices: newChoices} } return draft })) } // 设置答案的回调 const setAnswer = (questionId, answerIndex) => { setDrafts(prev => prev.map(draft => draft.id === questionId ? {...draft, answer: answerIndex} : draft )) } return ( <section> {/* Other Codes */} <ul className="flex flex-col gap-8"> {drafts.map((draft) => ( // 使用唯一id作为key <li key={draft.id}> {/* 渲染组件时传递数据和回调 */} <MultipleChoiceDraft question={draft.question} answer={draft.answer} choices={draft.choices} onUpdateQuestion={(text) => updateQuestion(draft.id, text)} onUpdateChoice={(index, text) => updateChoice(draft.id, index, text)} onSetAnswer={(index) => setAnswer(draft.id, index)} /> <button onClick={() => remove(draft.id)}>Remove Question</button> </li> ))} </ul> </section> ) }
MultipleChoiceDraft.js
// 受控组件,接收props中的数据和回调 const MultipleChoiceDraft = ({ question, answer, choices, onUpdateQuestion, onUpdateChoice, onSetAnswer }) => { return ( <form> <input placeholder="Question" value={question} onChange={(e) => onUpdateQuestion(e.target.value)} /> <ul> {Object.values(choices).map((choice, index) => ( <li key={index}> <input value={choice} type="text" onChange={(e) => onUpdateChoice(index, e.target.value)} /> <button type="button" onClick={(e) => { e.preventDefault(); // 阻止表单默认提交 onSetAnswer(index) }} > Set as Answer </button> </li> ))} </ul> </form> ) }
关键修复点解释
- 状态存储优化:
drafts现在存储纯数据,React可通过数据变化正确触发组件的渲染与卸载,删除问题时对应数据被移除,关联的MultipleChoiceDraft组件也会被正确卸载,输入内容不会残留。 - 受控组件设计:子组件不再维护内部状态,所有数据由父组件统一管理,确保数据与UI完全同步。
- 唯一key使用:用问题的
id作为key,即使数组元素顺序变化,React也能准确识别每个组件,避免复用错误的组件实例。
内容的提问来源于stack exchange,提问作者user22362540
相关产品推荐
相关产品推荐

