Next.js测验应用按钮点击数组重复洗牌及选项获取问题求助
问题解决方案
核心问题分析
- 重复洗牌导致选项混乱:每次组件渲染(点击按钮触发状态更新时)都会重新执行数组洗牌逻辑,导致选项数组在每次交互后都被重新打乱,索引对应的答案完全不可靠。
- 选项值与选择逻辑不匹配:按钮的点击事件中设置的
chosenOption和按钮显示的选项内容对应错误(比如显示newArray[0]却设置chosenOption为newArray[2]),加上数组频繁洗牌,彻底导致选择值错误。
具体修复步骤
1. 将当前题目的选项数组存入状态,仅在题目切换时生成并洗牌
把生成和洗牌选项数组的逻辑放到依赖number的useEffect中,确保只有切换题目时才重新生成选项数组,避免每次渲染都打乱:
// 新增状态保存当前题目的选项数组 const [currentOptions, setCurrentOptions] = useState([]); useEffect(() => { // 合并正确答案和错误答案,去重后洗牌 const allOptions = [...new Set([ ...quizData[number].incorrectAnswers, quizData[number].correctAnswer ])]; // 使用Fisher-Yates洗牌算法替代sort随机,保证概率公平 const shuffledOptions = [...allOptions]; for (let i = shuffledOptions.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffledOptions[i], shuffledOptions[j]] = [shuffledOptions[j], shuffledOptions[i]]; } setCurrentOptions(shuffledOptions); // 切换题目时重置选中状态 setChosenOption(""); }, [number, quizData]);
2. 修复选项按钮的选择逻辑,直接传递显示的选项值
每个按钮点击时直接把当前显示的选项值传给setChosenOption,不再依赖数组索引:
// 修改选项列表部分的代码 <div className={styles.list}> <ul className={styles.list1}> <div className={styles.flex}> <h3>A. </h3> <button onClick={() => setChosenOption(currentOptions[0])}> <li>{currentOptions[0]} </li> </button> </div> <div className={styles.flex}> <h3>B. </h3> <button onClick={() => setChosenOption(currentOptions[1])}> <li> {currentOptions[1]} </li> </button> </div> </ul> <ul className={styles.list2}> <div className={styles.flexOption}> <h2>C. </h2> <button onClick={() => setChosenOption(currentOptions[2])}> <li>{currentOptions[2]}</li> </button> </div> <div className={styles.flexOption}> <h2>D. </h2> <button onClick={() => setChosenOption(currentOptions[3])}> <li>{currentOptions[3]} </li> </button> </div> </ul> </div>
3. 修正得分判断逻辑,直接对比选中值和当前题目的正确答案
在increase和finish函数中,直接用当前题目的正确答案对比chosenOption,不再依赖newArray:
const increase = (e) => { e.preventDefault(); // 判断当前选项是否正确 if (chosenOption === quizData[number].correctAnswer) { setScore(prev => prev + 1); } setNumber(number + 1); setNumbering(numbering + 1); }; const finish = () => { if (chosenOption === quizData[number].correctAnswer) { setScore(prev => prev + 1); } };
4. 移除冗余代码
删除组件顶层的oldArray、newArray定义,以及原来的洗牌逻辑,避免干扰。
完整修改后的代码
import axios from "axios"; import Axios from "axios"; import React, { useContext, useEffect, useState } from "react"; import styles from "../../styles/Questions.module.css"; import Login from "../Login"; import { AppContext } from "../helpers/helpers"; import Link from "next/link"; import Solutions from "../solutions"; export default function quiz({ questions }) { const [number, setNumber] = useState(0); const [quizData, setQuizData] = useState(questions); const [state, setState] = useState(); const [image, setImage] = useState(); const [numbering, setNumbering] = useState(1); const [initialRenderComplete, setInitialRenderComplete] = useState(false); const [chosenOption, setChosenOption] = useState(""); const [showAnswer, setShowAnswer] = useState(false); const [currentOptions, setCurrentOptions] = useState([]); const { score, setScore } = useContext(AppContext); useEffect(() => { if (localStorage) { const getNameState = localStorage.getItem("name"); setState(getNameState); } else { console.log("errr"); } }, []); useEffect(() => { if (localStorage) { const getPhotoState = localStorage.getItem("photoUrl"); setImage(getPhotoState); } else { console.log("err"); } }, []); useEffect(() => { setInitialRenderComplete(true); }, []); // 切换题目时生成并洗牌选项数组 useEffect(() => { const allOptions = [...new Set([ ...quizData[number].incorrectAnswers, quizData[number].correctAnswer ])]; // Fisher-Yates洗牌算法 const shuffledOptions = [...allOptions]; for (let i = shuffledOptions.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffledOptions[i], shuffledOptions[j]] = [shuffledOptions[j], shuffledOptions[i]]; } setCurrentOptions(shuffledOptions); setChosenOption(""); }, [number, quizData]); const showAns = (e) => { e.preventDefault(); setShowAnswer(!showAnswer); }; const increase = (e) => { e.preventDefault(); if (chosenOption === quizData[number].correctAnswer) { setScore(prev => prev + 1); } setNumber(number + 1); setNumbering(numbering + 1); }; const finish = () => { if (chosenOption === quizData[number].correctAnswer) { setScore(prev => prev + 1); } }; const decrease = () => { setNumber(number - 1); setNumbering(numbering - 1); }; if (!initialRenderComplete || currentOptions.length === 0) { return null; } return ( <div className={styles.container}> <div className={styles.profile}> <div className={styles.mainProfile}> <div className={styles.profilePic}> <img src={image} alt="img" /> </div> <h2>{state} </h2> </div> </div> <div className={styles.main}> <h2> {numbering}. {questions[number].question}{" "} </h2> <div className={styles.list}> <ul className={styles.list1}> <div className={styles.flex}> <h3>A. </h3> <button onClick={() => setChosenOption(currentOptions[0])}> <li>{currentOptions[0]} </li> </button> </div> <div className={styles.flex}> <h3>B. </h3> <button onClick={() => setChosenOption(currentOptions[1])}> <li> {currentOptions[1]} </li> </button> </div> </ul> <ul className={styles.list2}> <div className={styles.flexOption}> <h2>C. </h2> <button onClick={() => setChosenOption(currentOptions[2])}> <li>{currentOptions[2]}</li> </button> </div> <div className={styles.flexOption}> <h2>D. </h2> <button onClick={() => setChosenOption(currentOptions[3])}> <li>{currentOptions[3]} </li> </button> </div> </ul> </div> <div className={styles.btnStyle}> <button onClick={decrease} className={styles.prev} disabled={number === 0}> Previous{" "} </button> {numbering == quizData.length ? ( <Link href="./scores"> <button className={styles.next} onClick={finish}> {" "} Finish Quiz{" "} </button> </Link> ) : ( <button onClick={increase} className={styles.next}> Next{" "} </button> )} {showAnswer ? ( <button onClick={showAns} className={styles.next}> Hide Answer{" "} </button> ) : ( <button onClick={showAns} className={styles.next}> See Answer{" "} </button> )} </div> <p> {showAnswer ? quizData[number].correctAnswer : ""}</p> </div> </div> ); } export const getStaticProps = async () => { const data = await Axios.get("https://the-trivia-api.com/api/questions"); const initialData = data.data; return { props: { questions: initialData, }, }; };
额外优化点
- 使用Fisher-Yates洗牌算法替代
sort(() => Math.random() - 0.5),后者存在概率不均的问题,洗牌结果不够随机。 - 给Previous按钮添加
disabled={number === 0},避免题目索引小于0的错误。 - 更新
setScore时使用函数式更新(setScore(prev => prev + 1)),确保得分计算基于最新状态。
内容的提问来源于stack exchange,提问作者Mayowa
相关产品推荐
相关产品推荐

