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

Next.js测验应用按钮点击数组重复洗牌及选项获取问题求助

问题解决方案

核心问题分析

  1. 重复洗牌导致选项混乱:每次组件渲染(点击按钮触发状态更新时)都会重新执行数组洗牌逻辑,导致选项数组在每次交互后都被重新打乱,索引对应的答案完全不可靠。
  2. 选项值与选择逻辑不匹配:按钮的点击事件中设置的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 19:01:17