我正在练习 React useState hooks 来进行一个测验,每个问题有 10 秒的计时器。
目前,我可以从 API 获取测验问题,将其设置为状态并呈现它们。如果用户单击答案,问题将从状态数组中删除,状态秒数重置为 10,然后呈现下一个问题。
当状态中的问题数组中没有任何内容时,我试图让计时器清除。当我在 startTimer 函数中使用 console.log(questions) 时,它是一个空数组,尽管相同的 console.log 显示了 userAnswer 函数中的数据?我在这里缺少什么?
我删除了引用的随机播放函数以节省空间
function App() {
// State for trivia questions, time left, right/wrong count
const [questions, setQuestions] = useState([])
const [seconds, setSeconds] = useState(10);
const [correct, setCorrect] = useState(0);
const [incorrect, setIncorrect] = useState(0);
// Get data, set questions to state, start timer
const getQuestions = async () => {
let trivia = await axios.get("https://opentdb.com/api.php?amount=10&category=9&difficulty=easy&type=multiple")
trivia = trivia.data.results
trivia.forEach(result => {
result.answers = shuffle([...result.incorrect_answers, result.correct_answer])
})
setQuestions(trivia)
startTimer()
}
const startTimer = () => {
// Empty array here, but data at beginning of userAnswer
console.log(questions)
const interval = setInterval(() => {
// If less than 1 second, increment incorrect, splice current question from state, clear interval and start timer back at 10
setSeconds(time => {
if (time < 1) {
setIncorrect(wrong => wrong + 1)
setQuestions(q => {
q.splice(0,1)
return q;
})
clearInterval(interval);
startTimer()
return 10;
}
// I want this to hold the question data, but it is not
if (questions.length === 0) {
console.log("test")
}
// Else decrement seconds
return time - 1;
});
}, 1000);
}
// If answer is right, increment correct, else increment incorrect
// Splice current question from const questions
const userAnswer = (index) => {
// Shows array of questions here
console.log(questions)
if (questions[0].answers[index] === questions[0].correct_answer) {
setCorrect(correct => correct + 1)
} else {
setIncorrect(incorrect => incorrect + 1)
}
setQuestions(q => {
q.splice(0,1);
return q;
})
// Shows same array here despite splice in setQuestions above
console.log(questions)
setSeconds(10);
}
return (
<div>
<button onClick={() => getQuestions()}></button>
{questions.length > 0 &&
<div>
<Question question={questions[0].question} />
{questions[0].answers.map((answer, index) => (
<Answer
key={index}
answer={answer}
onClick={() => userAnswer(index)} />
))}
</div>
}
<p>{seconds}</p>
<p>Correct: {correct}</p>
<p>Incorrect: {incorrect}</p>
</div>
)
}
export default App;
最佳答案
App
的每次渲染都将创建其内部函数和变量的新绑定(bind)(例如 questions
和 startTimer
)。
当单击按钮并且 getQuestions
运行时,getQuestions
然后调用 startTimer
函数。此时,startTimer
函数会关闭单击按钮时定义的 questions
变量 - 但它是空的那一点。在间隔内,虽然函数已重新渲染,但间隔回调仍然引用旧闭包中的问题
。
如果 questions
数组已填充。
另一个问题是 splice
不应该与 React 一起使用,因为它会改变现有数组 - 请改用非改变方法,例如 slice
.
尝试如下操作:
// Get data, set questions to state, start timer
const getQuestions = async () => {
let trivia = await axios.get("https://opentdb.com/api.php?amount=10&category=9&difficulty=easy&type=multiple")
trivia = trivia.data.results
trivia.forEach(result => {
result.answers = shuffle([...result.incorrect_answers, result.correct_answer])
})
setQuestions(trivia);
setSeconds(10);
}
useLayoutEffect(() => {
if (!questions.length) return;
// Run "timer" if the quiz is active:
const timeoutId = setTimeout(() => {
setSeconds(time => time - 1);
}, 1000);
return () => clearTimeout(timeoutId);
});
// If quiz is active and time has run out, mark this question as wrong
// and go to next question:
if (questions.length && time < 1) {
setIncorrect(wrong => wrong + 1)
setQuestions(q => q.slice(1));
setSeconds(10);
}
if ((correct || incorrect) && !questions.length) {
// Quiz done
console.log("test")
}
关于javascript - 使用 useState 的函数之间状态不持久,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63607859/