You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:45:30