反应渲染和状态

时间:2020-10-13 22:22:28

标签: javascript reactjs state

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

我有一组问题,我想选择一个从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的所有可能数字。

但是当我包含最后一行将状态设置为那些随机数字的最后一行时,似乎页面的重新渲染会重置整个功能,从而忘记了不重复并且忘记了所有先前的逻辑。

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

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

3 个答案:

答案 0 :(得分:0)

问题在于,您以前的随机数在每个渲染周期都会被擦除。您可以在反应生命周期之外“持久化”它们。

创建一个生成随机值并维护自己的最新生成的随机数缓存的函数。

const randomNumberGenerator = () => {
  let previous;

  return max => {
    let randomNumber;
    do {
      randomNumber = Math.floor(Math.random() * max);
    } while(randomNumber === previous);
    previous = randomNumber;
    return randomNumber;
  }
};

然后在需要获取随机的,非连续的值时,在react组件中添加

const getRandomNumber = randomNumberGenerator();

...

getRandomNumber(8)
getRandomNumber(10)
// etc..

Edit react-rendering-and-state

如果您知道最大值永远不会改变,即 总是 为8,则可以将max移至外部工厂功能。

const randomNumberGenerator = max => {
  let previous;

  return () => {
    let randomNumber;
    do {
      randomNumber = Math.floor(Math.random() * max);
    } while(randomNumber === previous);
    previous = randomNumber;
    return randomNumber;
  }
};

然后将代码中的用法简化一些。

const getRandomNumber = randomNumberGenerator(8);

...

getRandomNumber()

如果您仍然需要处理一定范围的随机数,则向函数添加min值并计算范围内的随机值。

const randomNumberGenerator = () => {
  let previous;

  return (min, max) => {
    let randomNumber;
    do {
      randomNumber = Math.floor(Math.random() * (max - min) + min);
    } while (randomNumber === previous);
    previous = randomNumber;
    return randomNumber;
  };
};

答案 1 :(得分:0)

尝试使用此挂钩存储访问的号码列表:

const [numbers,setVisited] = useState([]) .....

将您的号码添加到“访问过的”数组中,并在单击按钮时克隆一个数组(在比较引用的同时,采取黑客手段/重新渲染的方式)

setVisited(numbers.slice(0))

答案 2 :(得分:0)

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

我了解到我不是在直接回答您有关当前代码的问题,但我认为您的高级问题是您正在从错误的方向解决问题。

您的方法如下:

component => create data => render

通常最好的方法如下:

receive data => component => render

我认为您的问题实际上是“我如何随机播放一系列项目?”。然后,剩下的问题取决于您如何决定呈现它以及如何响应用户交互。

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

然后,您的组件将接受此数据作为道具,或者可能从网络中获取它们。他们来自哪里,都没关系。

有了这些数据,我们可以说“好的,我们呈现的初始状态是该数据的随机排序,又称“随机”。”

  // 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>
  );
};