简体   繁体   English

反应渲染和状态

[英]react rendering and state

While I understand the use of state and rendering a component, I'm having trouble with this particular situation.虽然我了解状态的使用和呈现组件,但我在这种特殊情况下遇到了麻烦。

I have a set of questions and I want to pick a random number from 0 to the question length (in my case 8) without the numbers repeating.我有一组问题,我想从 0 到问题长度(在我的情况下为 8)中选择一个随机数,而不重复数字。

I have figured that logic out but when I assign the random numbers to the state it seems that the re-rendering is causing this logic to reset each time and thus the numbers repeat.我已经弄清楚了这个逻辑,但是当我将随机数分配给状态时,似乎重新渲染导致此逻辑每次都重置,因此数字重复。 i need to THEN link to the question id with the corresponding random number.我需要然后链接到带有相应随机数的问题 ID。 or something of this nature.或这种性质的东西。

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

Everything up to the last line works.直到最后一行的所有内容都有效。

It's giving me a random number and the button I have which executes the function (thus giving the random number)它给了我一个随机数和执行该功能的按钮(因此给出了随机数)

DOES NOT repeat the random numbers and gives me something like 0 then 4 then 1 etc. each time I click it until it gives me all the possible numbers from 0 to 7.不重复随机数并给我类似 0 然后 4 然后 1 等的东西。每次我点击它,直到它给我从 0 到 7 的所有可能的数字。

But when I include the last line which sets the state to those random numbers each click it seems the re-rendering of the page resets this entire function, thus forgetting to NOT repeat and forgetting all the previous logic.但是,当我包含将状态设置为每次单击的随机数的最后一行时,似乎页面的重新呈现会重置整个功能,从而忘记不重复并忘记所有以前的逻辑。

To clarify something: this needs to be done with state because I want to set a random question to this random number state, then render a random question without it repeating (think of a basic quiz).澄清一些事情:这需要用状态来完成,因为我想将一个随机问题设置为这个随机数状态,然后呈现一个随机问题而不重复(想想一个基本的测验)。

I also do not want the amount of numbers set or determined.我也不想设置或确定数字的数量。 It needs to work dynamically considering I will be adding more questions over time to the quiz.考虑到随着时间的推移我会在测验中添加更多问题,它需要动态地工作。

The issue is that your previous random numbers are wiped each render cycle.问题是您之前的随机数在每个渲染周期都会被擦除。 You can "persist" them outside the react lifecycle.您可以在 React 生命周期之外“持久化”它们。

Create a function that generates a random value and maintains its own cache of last random number generated.创建一个生成随机值的函数,并维护自己的最后生成的随机数缓存。

const randomNumberGenerator = () => {
  let previous;

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

Then in your react component when you need to grab a random, non-consecutive value:然后在你的 React 组件中,当你需要获取一个随机的、非连续的值时:

const getRandomNumber = randomNumberGenerator();

...

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

编辑反应渲染和状态

If you know your max will never change, ie it will always be 8, then you can move max to the outer factory function.如果你知道你的 max 永远不会改变,即它总是8,那么你可以将max移动到外部工厂函数。

const randomNumberGenerator = max => {
  let previous;

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

Then the usage in code is simplified a little.然后代码中的用法稍微简化了。

const getRandomNumber = randomNumberGenerator(8);

...

getRandomNumber()

If you still need to handle a range of random numbers then add a min value to the function and compute the random value in range.如果您仍然需要处理一系列随机数,则向函数添加一个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;
  };
};

Try this hook to store visited numbers list:试试这个钩子来存储访问过的号码列表:

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

add your number to a "visited" array and clone an array on a button click (react hacks/ways to rerender, while references are compared)将您的号码添加到“已访问”数组并在单击按钮时克隆一个数组(反应黑客/重新渲染的方法,同时比较引用)

setVisited(numbers.slice(0)) setVisited(numbers.slice(0))

Check out this demo I threw together: https://codesandbox.io/s/competent-pine-0hxxi?file=/src/index.tsx看看我拼凑的这个演示: https : //codesandbox.io/s/competent-pine-0hxxi?file=/ src/ index.tsx

I understand I'm not answering your question about your current code directly, but I think your high-level issue is that you're approaching the problem from the wrong direction.我知道我没有直接回答您关于当前代码的问题,但我认为您的高级问题是您从错误的方向解决问题。

Your approach looks like this:您的方法如下所示:

component => create data => render

When often the best way to do it looks like this:通常最好的方法是这样的:

receive data => component => render

I think your problem is actually "How do I shuffle an array of items?".我认为您的问题实际上是“如何洗牌一组项目?”。 The rest of your problem then lies with how you decide to present it, and response to user interaction.剩下的问题在于你决定如何呈现它,以及对用户交互的响应。

Before we start thinking about your component, your questions are defined somewhere.在我们开始考虑您的组件之前,您的问题已在某处定义。 Let's call this initial pool of questions data .我们将这个初始问题池称为data

Your component will then accept this data either as props, or it might fetch them from the network.然后你的组件将接受这些数据作为道具,或者它可能从网络中获取它们。 Where ever they come from, it doesn't really matter.它们来自哪里,这并不重要。

With this data, we can say "OK our initial state for presentation is a random ordering of this data, aka '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])
  );

OK, so we have our 'random' (shuffled) questions.好的,所以我们有我们的“随机”(随机)问题。 We don't need to worry about this at all anymore.我们完全不需要再担心这个了。

If I've missed the fact you want it to continue to shuffle the questions as each gets answered, happy to expand on my answer further.如果我错过了你希望它在每个问题得到回答时继续洗牌的事实,很高兴进一步扩展我的答案。

Now all we need to do is display them how ever we like.现在我们需要做的就是以我们喜欢的方式展示它们。 If we're only showing one question at a time, we need to keep track of that.如果我们一次只显示一个问题,我们需要跟踪它。

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

When a user selects or gives an answer to the question, we can simply replace that question with our answered question.当用户选择或给出问题的答案时,我们可以简单地用我们回答的问题替换该问题。 This is how React state likes to work;这就是 React 状态喜欢的工作方式; don't mutate what you already have, just throw everything at it all over again.不要改变你已经拥有的东西,只是把所有东西都扔一遍。

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

The rest just rests on letting users navigate through your array of questions.剩下的就是让用户浏览您的一系列问题。 Here's the full component:这是完整的组件:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM