React中如何搭建分步问答表单并实现答题框切换?
解决方案
1. 重构数据结构,关联标题与选项
把分散的标题数组和选项数组整合成结构化数组,每个元素对应一道题的标题和选项,通过counter就能直接同步获取当前题的完整信息,彻底解决数组索引不匹配的问题:
// 结构化题目数据:每一项包含标题和对应的选项 const questions = [ { title: "title1", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] }, { title: "title2", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] }, { title: "title3", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] }, { title: "title4", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] } ];
2. 简化状态管理
不需要单独维护title和qBoxes状态,直接通过counter从questions数组中提取当前题信息,减少冗余状态:
function ReportForm(props) { // 题目计数器,初始指向第0题 const [counter, setCounter] = useState(0); // 存储所有题的答案:数组索引对应题目序号,值为该题选中的选项数组 const [answers, setAnswers] = useState(Array(questions.length).fill([])); // ... 后续逻辑 }
3. 完善答题选中与收集逻辑
- 处理选项的选中/取消状态,按题目序号存储答案,确保切换题目后保留之前的选择
- 避免直接修改原数组(如
pop),采用React推荐的不可变更新方式:
const handleClick = (event) => { const optionValue = event.currentTarget.value; const currentQuestionAnswers = answers[counter]; let updatedAnswers; if (event.currentTarget.classList.contains('active')) { // 取消选中:过滤掉当前选项 updatedAnswers = currentQuestionAnswers.filter(val => val !== optionValue); event.currentTarget.classList.remove('active'); } else { // 选中:添加当前选项 updatedAnswers = [...currentQuestionAnswers, optionValue]; event.currentTarget.classList.add('active'); } // 更新对应题目的答案 const newAnswers = [...answers]; newAnswers[counter] = updatedAnswers; setAnswers(newAnswers); };
4. 修正Next/Previous按钮逻辑
处理边界条件(第一题/最后一题),避免无效操作,同时优化状态更新方式:
const submitNext = () => { if (counter < questions.length - 1) { setCounter(prev => prev + 1); } }; const submitPrev = () => { if (counter > 0) { setCounter(prev => prev - 1); } };
5. 渲染时自动恢复选中状态
切换回之前的题目时,自动恢复该题已选中选项的样式:
return ( <div> {/* 显示当前题的标题 */} <p>{questions[counter].title}</p> {/* 渲染当前题的选项,自动恢复选中状态 */} {questions[counter].options.map((option) => ( <button key={option} // 添加唯一key,符合React渲染规范 onClick={handleClick} className={`boxStyle ${answers[counter].includes(option) ? 'active' : ''}`} name="contentYes" value={option} > {option} </button> ))} <button className="buttonNext" onClick={submitNext} disabled={counter === questions.length - 1} // 最后一题时禁用Next >Next</button> <button className="buttonPrev" onClick={submitPrev} disabled={counter === 0} // 第一题时禁用Previous >Previous</button> </div> );
完整修正代码
import { useState } from 'react'; // 结构化题目数据 const questions = [ { title: "title1", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] }, { title: "title2", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] }, { title: "title3", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] }, { title: "title4", options: ["answer 1", "answer 2", "answer 3", "answer 4", "answer 5", "answer 6"] } ]; function ReportForm(props) { const [counter, setCounter] = useState(0); // 初始化答案数组,每个元素对应一道题的选中选项 const [answers, setAnswers] = useState(Array(questions.length).fill([])); const handleClick = (event) => { const optionValue = event.currentTarget.value; const currentAnswers = answers[counter]; let updatedCurrentAnswers; if (event.currentTarget.classList.contains('active')) { updatedCurrentAnswers = currentAnswers.filter(val => val !== optionValue); event.currentTarget.classList.remove('active'); } else { updatedCurrentAnswers = [...currentAnswers, optionValue]; event.currentTarget.classList.add('active'); } // 不可变更新答案数组 const newAnswers = [...answers]; newAnswers[counter] = updatedCurrentAnswers; setAnswers(newAnswers); }; const submitNext = () => { if (counter < questions.length - 1) { setCounter(prev => prev + 1); } }; const submitPrev = () => { if (counter > 0) { setCounter(prev => prev - 1); } }; return ( <div> <p>{questions[counter].title}</p> {questions[counter].options.map((option) => ( <button key={option} onClick={handleClick} className={`boxStyle ${answers[counter].includes(option) ? 'active' : ''}`} name="contentYes" value={option} > {option} </button> ))} <button className="buttonNext" onClick={submitNext} disabled={counter === questions.length - 1} >Next</button> <button className="buttonPrev" onClick={submitPrev} disabled={counter === 0} >Previous</button> </div> ); } export default ReportForm;
核心改进说明
- 数据结构化:彻底解决标题与选项数组不同步的问题,通过
counter直接定位当前题的所有信息 - 答案按题存储:避免不同题的答案混淆,切换题目后能完全保留之前的选择状态
- 不可变状态更新:遵循React状态更新规范,避免直接修改原数组导致的状态异常
- 边界交互优化:禁用边界状态的按钮,提升用户操作体验
- 自动恢复选中样式:切换回旧题目时,自动恢复之前选中的选项样式
内容的提问来源于stack exchange,提问作者nordan
相关产品推荐
相关产品推荐

