React点击答题选项时答案数组重复洗牌问题求助
问题描述
我是React新手,开发了一个<QuizPage />组件,包含子组件<TriviaObject />,子组件接收题目、正确答案(字符串)、错误答案数组三个属性。API返回的数据模型示例:
{ "question": "question", "correct_answer": "answer", "incorrect_answers": [ "answer1", "answer2", "answer3", ... ] }
当前遇到两个问题:
- 点击任意答案选项时,页面上的答案数组会重新洗牌,期望点击仅将选项存入
userSelection状态,用于后续提交统计正确率。 - 代码中添加了多处
console.log但控制台无输出,怀疑状态或useEffect未正常工作。
新增userSelection状态存储每题用户选择后出现了上述问题,以下是代码实现:
QuizPage组件代码
export default function QuizPage() { const [quiz, setQuiz] = useState([]); // state to store user's selected answer for each question const [userSelection, setUserSelection] = useState([]) useEffect(() => { async function getData() { try { const res = await fetch("https://opentdb.com/api.php?amount=10") const data = await res.json() setQuiz(data.results) } catch (error) { console.error("Error fetching data:", error) } } getData() }, []) if (!quiz || quiz.length === 0) { return <div>Loading...</div>; // or any loading indicator } const handleAnswerSelected = (answer, index) => { console.log(`Selected Answer for Question ${index + 1}:`, answer) // update the userSelection state array with the selected answer const updatedSelection = [...userSelection]; updatedSelection[index] = answer; setUserSelection(updatedSelection); console.log("Update userSelection:", updatedSelection) } const renderTrivia = quiz.map((object, index) => { console.log("Rendering TriviaObject with props:", object) return ( <TriviaObject key={index} question={decodeHTML(object.question)} correct_answer={decodeHTML(object.correct_answer)} incorrect_answers={object.incorrect_answers.map(item => decodeHTML(item))} onAnswerSelected={(answer) => handleAnswerSelected(answer, index)} /> )}) return ( <div className="quiz-page"> {renderTrivia} <button>Submit Answers</button> </div> ) }
TriviaObject组件代码
export default function TriviaObject(props) { const { question, onAnswerSelected, correct_answer, incorrect_answers } = props; const [allAnswers, setAllAnswers] = useState([]); // Apply shuffle only once when the component mounts useEffect(() => { console.log("shuffling answers..."); const shuffledArray = shuffle([correct_answer, ...incorrect_answers]); setAllAnswers(shuffledArray); }, [correct_answer, incorrect_answers]); // Fisher-Yates shuffle alrgorithm function shuffle(array) { const shuffledArray = [...array]; for (let i = shuffledArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); const temp = shuffledArray[i]; shuffledArray[i] = shuffledArray[j]; shuffledArray[j] = temp; } return shuffledArray; } return ( <div className="trivia-obj"> <h2>{question}</h2> <ul> {allAnswers.map((answer, index) => { const styles = { backgroundColor: onAnswerSelected[index] === answer ? '#D6DBF5' : '' , border: onAnswerSelected[index] === answer ? 'none' : '1px solid #D6DBF5' }; return ( <li key={index} onClick={() => onAnswerSelected(answer)} style={styles} > {answer} </li> ); })} </ul> </div> ); }
解决方案
问题根源分析
- 答案重新洗牌:每次
userSelection更新触发QuizPage重渲染时,incorrect_answers会重新执行map(decodeHTML)生成新数组,导致TriviaObject的useEffect依赖项变化,触发重新洗牌。 - console.log无输出:
TriviaObject中错误将回调函数onAnswerSelected当作数组访问onAnswerSelected[index],引发静默报错,阻断了日志输出。
修复代码
修改后的QuizPage组件
export default function QuizPage() { const [quiz, setQuiz] = useState([]); const [userSelection, setUserSelection] = useState([]); useEffect(() => { async function getData() { try { const res = await fetch("https://opentdb.com/api.php?amount=10"); const data = await res.json(); // 提前解码所有数据,避免每次渲染生成新数组 const decodedQuiz = data.results.map(item => ({ question: decodeHTML(item.question), correct_answer: decodeHTML(item.correct_answer), incorrect_answers: item.incorrect_answers.map(i => decodeHTML(i)) })); setQuiz(decodedQuiz); } catch (error) { console.error("Error fetching data:", error); } } getData(); }, []); if (!quiz || quiz.length === 0) { return <div>Loading...</div>; } const handleAnswerSelected = (answer, index) => { console.log(`Selected Answer for Question ${index + 1}:`, answer); const updatedSelection = [...userSelection]; updatedSelection[index] = answer; setUserSelection(updatedSelection); console.log("Updated userSelection:", updatedSelection); }; const renderTrivia = quiz.map((object, index) => { console.log("Rendering TriviaObject with props:", object); return ( <TriviaObject key={index} question={object.question} correct_answer={object.correct_answer} incorrect_answers={object.incorrect_answers} selectedAnswer={userSelection[index]} onAnswerSelected={(answer) => handleAnswerSelected(answer, index)} /> ); }); return ( <div className="quiz-page"> {renderTrivia} <button>Submit Answers</button> </div> ); }
修改后的TriviaObject组件
import { useMemo } from 'react'; export default function TriviaObject(props) { const { question, onAnswerSelected, correct_answer, incorrect_answers, selectedAnswer } = props; // 用useMemo缓存洗牌结果,仅在初始数据变化时重新洗牌 const allAnswers = useMemo(() => { console.log("shuffling answers..."); const shuffle = (array) => { const shuffledArray = [...array]; for (let i = shuffledArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; } return shuffledArray; }; return shuffle([correct_answer, ...incorrect_answers]); }, [correct_answer, incorrect_answers]); return ( <div className="trivia-obj"> <h2>{question}</h2> <ul> {allAnswers.map((answer, index) => { const styles = { backgroundColor: selectedAnswer === answer ? '#D6DBF5' : '', border: selectedAnswer === answer ? 'none' : '1px solid #D6DBF5' }; return ( <li key={index} onClick={() => onAnswerSelected(answer)} style={styles} > {answer} </li> ); })} </ul> </div> ); }
额外优化建议
- 避免使用数组索引作为
key,如果API返回题目唯一标识(如question字段的哈希值),改用唯一标识作为key提升渲染稳定性。 - 将
shuffle函数提取到组件外部,避免每次渲染重复定义函数。
内容的提问来源于stack exchange,提问作者AlmondButter
相关产品推荐
相关产品推荐

