React Quiz应用中点击选项后Option组件重复渲染问题求助
问题分析与解决方案
核心问题原因
- 不稳定的组件Key:QuizScreen中渲染题目和选项时使用
nanoid()生成key,每次组件渲染都会生成全新的key,导致React判定为全新组件,会卸载旧组件并重新挂载而非更新,直接丢失Option的selected状态,表现为组件"自行重新渲染"。 - 未缓存的回调函数:
checkAnswer函数在App组件每次渲染时都会重新创建,作为props传递给子组件后,会触发QuizScreen和Option的不必要重渲染。 - 重复执行的选项排序:选项随机排序逻辑在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
相关产品推荐
相关产品推荐

