虽然我了解状态的使用和组件的呈现,但是在这种特殊情况下我遇到了麻烦。
我有一组问题,我想选择一个从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的所有可能数字。
但是当我包含最后一行将状态设置为那些随机数字的最后一行时,似乎页面的重新渲染会重置整个功能,从而忘记了不重复并且忘记了所有先前的逻辑。
为了澄清一点:这需要使用状态来完成,因为我想将随机问题设置为该随机数状态,然后呈现一个随机问题而不重复(考虑基本测验)。
我也不希望设置或确定数量的数字。考虑到随着时间的推移我将向测验中添加更多问题,它需要动态地工作。
答案 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..
如果您知道最大值永远不会改变,即 总是 为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>
);
};