React问答应用正确答案计数显示问题:解决重渲染与统计异常
React问答应用统计正确答案数量的问题解决
问题背景
开发React问答应用时,希望测验结束后显示用户答对的题目数量。原实现是在QuizItem组件维护correctCount状态,选中正确答案时更新并传递给父组件QuizList,但出现过度重渲染,用useEffect包裹后又无法正确统计数量。
解决方案
核心思路是将统计逻辑从子组件转移到父组件,利用父组件的quiz状态直接计算正确答案数,避免子组件重复更新状态,减少不必要的重渲染。
具体步骤
- 移除子组件的状态维护,让
QuizItem仅负责渲染和传递用户选择操作 - 父组件在用户点击"Check Answers"时,遍历
quiz数组统一统计正确答案数量 - 消除渲染周期中频繁触发的父组件回调调用,只在需要时执行统计逻辑
修改后的QuizItem组件
/* eslint-disable react/prop-types */ import React from "react"; import AnswerButton from "../UI/AnswerButton"; import classes from "./QuizItem.module.css"; export default function QuizItem(props) { function checkAnswerStyle(choice) { /* 选中的正确答案,按钮变绿 */ if (choice.isSelected && choice.choice === choice.correct) { return { backgroundColor: "#94D7A2", color: "#4D5B9E", border: "none", }; /* 选中的错误答案,按钮变红 */ } else if (choice.isSelected && choice.choice !== choice.correct) { return { backgroundColor: "#F8BCBC", color: "#4D5B9E", border: "none", }; /* 未选中但正确的答案,高亮显示 */ } else if (choice.choice === choice.correct) { return { backgroundColor: "#94D7A2", color: "#4D5B9E", border: "none", }; /* 错误答案置灰 */ } else { return { color: "#bfc0c0", border: "1px solid #bfc0c0", backgroundColor: "white", }; } } return ( <div key={props.id} className={classes.quizlist__quizitem}> <h3 className={classes.quizitem__h3}>{props.question}</h3> {props.choices.map((choice) => { const styles = { backgroundColor: choice.isSelected ? "#D6DBF5" : "white", }; return ( <AnswerButton key={choice.id} onClick={() => { props.holdAnswer(choice.id); }} style={props.endQuiz ? checkAnswerStyle(choice) : styles} > {choice.choice} </AnswerButton> ); })} </div> ); }
修改后的QuizList组件
import React from "react"; import { nanoid } from "nanoid"; import QuizItem from "./QuizItem"; import Button from "../UI/Button"; import Card from "../UI/Card"; import classes from "./QuizList.module.css"; export default function QuizList(props) { const [quiz, setQuiz] = React.useState([]); const [endQuiz, setEndQuiz] = React.useState(false); const [noOfCorrectAnswers, setNoOfCorrectAnswers] = React.useState(0); React.useEffect(() => { /* 解码HTML实体 */ function decodeHtml(html) { const txt = document.createElement("textarea"); txt.innerHTML = html; return txt.value; } fetch( "https://opentdb.com/api.php?amount=5&category=9&difficulty=medium&type=multiple" ) .then((res) => res.json()) .then((data) => { const dataArray = data.results; const newDataArray = dataArray.map((item) => { return { question: decodeHtml(item.question), choices: [ { choice: decodeHtml(item.correct_answer), isSelected: false, correct: decodeHtml(item.correct_answer), id: nanoid(), }, { choice: decodeHtml(item.incorrect_answers[0]), isSelected: false, correct: decodeHtml(item.correct_answer), id: nanoid(), }, { choice: decodeHtml(item.incorrect_answers[1]), isSelected: false, correct: decodeHtml(item.correct_answer), id: nanoid(), }, { choice: decodeHtml(item.incorrect_answers[2]), isSelected: false, correct: decodeHtml(item.correct_answer), id: nanoid(), }, ].sort(() => 0.5 - Math.random()), id: nanoid(), }; }); setQuiz(newDataArray); }); }, []); function calculateCorrectAnswers() { let count = 0; quiz.forEach((question) => { const selectedChoice = question.choices.find(choice => choice.isSelected); if (selectedChoice && selectedChoice.choice === selectedChoice.correct) { count++; } }); setNoOfCorrectAnswers(count); setEndQuiz(true); } function startNewGame() { setEndQuiz(false); setNoOfCorrectAnswers(0); // 重置所有题目选择状态 setQuiz(prevQuiz => prevQuiz.map(question => ({ ...question, choices: question.choices.map(choice => ({...choice, isSelected: false})) }))); } function holdAnswer(quizId, choiceId) { setQuiz((oldQuiz) => oldQuiz.map((quiz) => { if (quiz.id !== quizId) return quiz; return { ...quiz, choices: quiz.choices.map((choice) => choice.id === choiceId ? {...choice, isSelected: !choice.isSelected} : {...choice, isSelected: false} ), }; }) ); } const quizItemComponents = quiz.map((item) => { return ( <QuizItem key={item.id} question={item.question} choices={item.choices} holdAnswer={(id) => holdAnswer(item.id, id)} endQuiz={endQuiz} /> ); }); return ( <Card className={classes.quizlist}> {quizItemComponents} {!endQuiz && <Button onClick={calculateCorrectAnswers}>Check Answers</Button>} {endQuiz && ( <div className={classes.result}> <p>You scored {noOfCorrectAnswers}/5 answers</p> <Button onClick={startNewGame}>Play Again</Button> </div> )} </Card> ); }
关键优化说明
- 移除子组件状态:
QuizItem不再维护correctCount,避免了子组件频繁更新状态导致的重渲染,同时消除了状态不同步问题 - 父组件统一计算:点击"Check Answers"时,父组件直接从
quiz状态中统计正确答案数,逻辑清晰且避免重复计算 - 消除不必要的回调:原代码中每次渲染都触发父组件回调的逻辑被移除,仅在需要统计时执行一次计算
内容的提问来源于stack exchange,提问作者kebin20
相关产品推荐
相关产品推荐

