首页 > 解决方案 > 反应渲染和状态

问题描述

虽然我了解状态的使用和渲染组件,但我在这种特殊情况下遇到了麻烦。

我有一组问题,我想从 0 到问题长度(在我的情况下为 8)中选择一个随机数,而不需要重复数字。

我已经弄清楚了这个逻辑,但是当我将随机数分配给状态时,似乎重新渲染导致该逻辑每次都重置,因此数字重复。我需要使用相应的随机数链接到问题 ID。或这种性质的东西。

const SomeComponent = props => {
  const [random, setRandom] = useState(null)

  function getRandomNumber(min, max) {
    let stepOne = max - min + 1;
    let stepTwo = Math.random() * stepOne;
    let result = Math.floor(stepTwo) + min;

    return result
  }

  // this creates the array[0, 1, 2, 3, 4, 5, 6, 7]
  function createArrayOfNumbers(start, end) {
    let myArray = [];

    for (let i = start; i <= end; i++) {
      myArray.push(i)
    }

    return myArray
  }

  let numbers = []

  function generator() {
    let numbersArray = createArrayOfNumbers(0, qAndA.length - 1)
    let finalNumbers = [];

    while (numbersArray.length > 0) {
      let randomIndex = getRandomNumber(0, qAndA.length - 1)
      let randomNumber = numbersArray[randomIndex];
      numbersArray.splice(randomIndex, 1);
      finalNumbers.push(randomNumber)
    }
    for (let nums of finalNumbers) {
      if (typeof nums === 'number') {
        numbers.push(nums)
        // for some reason i was getting undefined for a few so i weeded them out here and pushed into a new array
      }
    }
    const tester = numbers.splice(0, 1) ** this part works fine and
    console.log(tester) // each time my button is pressed it console logs a non repeating random number until ALL numbers(from 0 to 7 are chosen)
    setRandom(tester) // THIS IS THE LINE THAT SCREWS IT UP.re rendering seems to toss all the previous logic out...
  }


  return (<button onClick={generator}>click this to call function</button>)
}

直到最后一行的所有内容都有效。

它给了我一个随机数和执行该功能的按钮(因此给出了随机数)

不重复随机数,每次单击它时都会给我 0 然后 4 然后 1 等等,直到它给我从 0 到 7 的所有可能数字。

但是,当我包含将状态设置为每次单击这些随机数的最后一行时,似乎页面的重新呈现会重置整个功能,从而忘记不重复并忘记所有先前的逻辑。

澄清一下:这需要用状态来完成,因为我想将一个随机问题设置为这个随机数状态,然后呈现一个随机问题而不重复(想想一个基本测验)。

我也不希望设置或确定的数字数量。考虑到我将随着时间的推移向测验添加更多问题,它需要动态工作。

标签: javascriptreactjsstate

解决方案


看看我放在一起的这个演示:https ://codesandbox.io/s/competent-pine-0hxxi?file=/src/index.tsx

我知道我没有直接回答您有关当前代码的问题,但我认为您的高级问题是您从错误的方向解决问题。

您的方法如下所示:

component => create data => render

通常最好的方法是这样的:

receive data => component => render

我认为您的问题实际上是“如何对一系列项目进行洗牌?”。剩下的问题在于你决定如何呈现它,以及对用户交互的响应。

在我们开始考虑您的组件之前,您的问题已在某处定义。我们称这个初始问题池为data

然后,您的组件将接受这些数据作为道具,或者它可能从网络中获取它们。他们来自哪里,这并不重要。

有了这些数据,我们可以说“好的,我们的初始呈现状态是这个数据的随机排序,也就是'shuffled'”。

  // given our question pool `data`
  // we can simply set the initial state to a shuffled version
  const [questions, setQuestions] = React.useState<Question[]>(
    shuffle([...data])
  );

好的,所以我们有我们的“随机”(随机)问题。我们完全不需要再担心这个了。

如果我错过了您希望它在每个问题得到回答时继续随机播放问题的事实,很高兴进一步扩展我的答案。

现在我们需要做的就是以我们喜欢的方式展示它们。如果我们一次只显示一个问题,我们需要跟踪它。

  // keep track of which question we're displaying right now
  const [qIndex, setQIndex] = React.useState<number>(0);

当用户选择或给出问题的答案时,我们可以简单地用我们回答的问题替换该问题。这就是 React 状态喜欢的工作方式;不要改变你已经拥有的东西,只是把所有东西都扔一遍。

  const handleAnswerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // create our updated question
    // now with an answer
    const theQuestion = questions[qIndex];
    const answeredQuestion = {
      ...theQuestion,
      answer: event.target.value
    };
    // copy our questions, and flip the old question for the new one
    const newQuestions = [...questions];
    newQuestions.splice(qIndex, 1, answeredQuestion);
    setQuestions(newQuestions);
  };

剩下的就是让用户浏览你的一系列问题。这是完整的组件:

interface QuizProps {
  data: Question[];
}

export const Quiz = (props: QuizProps) => {
  // keep track of which question we're displaying right now
  const [qIndex, setQIndex] = React.useState<number>(0);

  // given our question pool `data`
  // we can simply set the initial state to a shuffled version
  const [questions, setQuestions] = React.useState<Question[]>(
    shuffle([...props.data])
  );

  // create our updated question
  // now with an answer
  const handleAnswerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const theQuestion = questions[qIndex];
    const answeredQuestion = {
      ...theQuestion,
      answer: event.target.value
    };
    // copy our questions, and flip the old question for the new one
    // using slice (there are many ways to do this)
    const newQuestions = [...questions];
    newQuestions.splice(qIndex, 1, answeredQuestion);
    setQuestions(newQuestions);
  };

  const handleBackClick = () => setQIndex((i) => (i > 0 ? i - 1 : 0));
  const handleNextClick = () =>
    setQIndex((i) => (i < questions.length - 1 ? i + 1 : i));

  return (
    <div>
      <h1>Quiz</h1>
      <div>
        <h2>{questions[qIndex].title}</h2>
        <h3>
          Question {qIndex + 1} of {questions.length}
        </h3>
        <p>{questions[qIndex].description}</p>
        <ul>
          {questions[qIndex].options.map((answer, i) => (
            <li key={i}>
              <input
                id={answer.id}
                type="radio"
                name={questions[qIndex].id}
                checked={answer.id === questions[qIndex]?.answer}
                value={answer.id}
                onChange={handleAnswerChange}
              />
              <label htmlFor={answer.id}>{answer.value}</label>
            </li>
          ))}
        </ul>
      </div>
      <button onClick={handleBackClick}>Previous</button>
      <button onClick={handleNextClick} disabled={!questions[qIndex].answer}>
        Next
      </button>
    </div>
  );
};

推荐阅读