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

React Quiz应用中点击选项后Option组件重复渲染问题求助

问题分析与解决方案

核心问题原因

  1. 不稳定的组件Key:QuizScreen中渲染题目和选项时使用nanoid()生成key,每次组件渲染都会生成全新的key,导致React判定为全新组件,会卸载旧组件并重新挂载而非更新,直接丢失Option的selected状态,表现为组件"自行重新渲染"。
  2. 未缓存的回调函数:checkAnswer函数在App组件每次渲染时都会重新创建,作为props传递给子组件后,会触发QuizScreen和Option的不必要重渲染。
  3. 重复执行的选项排序:选项随机排序逻辑在QuizScreen的render阶段执行,每次渲染都会重新打乱选项顺序,进一步加剧组件状态丢失的问题。

分步修改方案

1. 优化App.js:缓存回调+提前处理选项

使用useCallback缓存回调函数,同时在数据获取阶段完成选项排序,避免render阶段重复执行:

import { useState, useEffect, useCallback } from 'react';
import QuizScreen from './QuizScreen';

export default function App() {
  const [questions, setQuestions] = useState([])
  const [score, setScore] = useState(0)

  useEffect(() => { 
    async function fetchQuestions(){
      const res = await fetch("https://opentdb.com/api.php?amount=10&category=18&difficulty=medium")
      const data = await res.json()
      // 提前整合并随机排序选项
      const processedQuestions = data.results.map(ques => {
        const options = [...ques.incorrect_answers, ques.correct_answer];
        // Fisher-Yates洗牌算法
        for (let i = options.length - 1; i > 0; i--) {
          const j = Math.floor(Math.random() * (i + 1));
          [options[i], options[j]] = [options[j], options[i]];
        }
        return { ...ques, options };
      });
      setQuestions(processedQuestions);
    }
    fetchQuestions()
  }, [])

  // 用useCallback缓存函数,仅依赖questions变化
  const checkAnswer = useCallback((option, questionIndex) => {
    if(option === questions[questionIndex].correct_answer){
      setScore(prevScore => prevScore + 5)
      console.log("correct")
    }
    else{
      setScore(prevScore => prevScore - 1)
      console.log("incorrect")
    }
  }, [questions])
  
  return (
    <QuizScreen questions={questions} score={score} checkAnswer={checkAnswer} />
  )
}

2. 优化QuizScreen.js:使用稳定Key+缓存渲染结果

移除nanoid(),改用题目和选项的唯一文本作为稳定key,同时用useMemo缓存渲染结果:

import { useMemo } from 'react';
import Option from './Option';

export default function QuizScreen(props) {
  // 用useMemo缓存渲染结果,仅在依赖项变化时重新计算
  const questionElements = useMemo(() => {
    return props.questions.map((ques, index) => {
      const optionElements = ques.options.map(opt => (
        <Option 
          key={opt} // 用选项文本作为唯一key
          option={opt} 
          questionIndex={index} 
          checkAnswer={props.checkAnswer} 
        />
      ))

      return (
        <div className="ques-container" key={ques.question}> {/* 用题目文本作为稳定key */}
            <p className="ques-title">{ques.question}</p>
            {optionElements}
        </div>
      )
    })
  }, [props.questions, props.checkAnswer])
  
  return (
    <div>
      <p>{props.score}</p>
      {questionElements}
    </div>
  )
}

3. 优化Option.js:用React.memo减少重渲染

用React.memo包裹Option组件,仅在props变化时触发重渲染:

import { useState, memo } from 'react';

const Option = memo(function Option(props) {
    const [selected, setSelected] = useState(false)

    const btnStyle = {
        backgroundColor: selected ? "#D6DBF5" : "#FFFFFF"
    }

    return (
        <button 
            className="ques-option"
            onClick={() => {
                props.checkAnswer(props.option, props.questionIndex)
                setSelected(prevState => !prevState)
            }}
            style={btnStyle}
        >
            {props.option}
        </button>
    )
})

export default Option;

关键说明

  • 提前在数据获取阶段处理选项排序,避免render阶段重复执行,既提升性能又防止选项顺序随机变化。
  • 使用稳定的key确保React能正确识别组件身份,保留组件内部状态。
  • useCallback和React.memo组合使用,大幅减少不必要的组件重渲染。

内容的提问来源于stack exchange,提问作者Anand Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:45:21