javascript - 状态不在使用 useState 的函数之间保持不变
问题描述
我正在练习 React useState hooks 来做一个每个问题十秒计时器的测验。
目前,我能够从 API 获取测验问题,将它们设置为状态,并呈现它们。如果用户单击答案,则问题将从状态数组中删除,状态秒数重置为 10 并呈现下一个问题。
当状态中的问题数组中没有任何内容时,我试图让计时器清除。当我在 startTimer 函数中使用 console.log(questions) 时,它是一个空数组,尽管相同的 console.log 在 userAnswer 函数中显示数据?我在这里想念什么?
我删除了引用的 shuffle 函数以节省空间
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
将创建其内部函数和变量(如questions
和startTimer
)的新绑定。
当按钮被单击并getQuestions
运行时,getQuestions
然后调用该startTimer
函数。在那个时间点,该startTimer
函数关闭了在questions
单击按钮时定义的变量——但此时它是空的。在区间内,虽然函数已经重新渲染,但区间回调仍然引用questions
旧闭包中的 。
如果填充了数组,我将启动一个 setTimeout
on render (在 a 内,useLayoutEffect
以便在需要时可以清除超时) ,而不是依赖于旧的闭包。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")
}
推荐阅读
- codeigniter - 如何在codeigniter中的数据库中显示dropzone中的图像
- docker - 传感器不会因工人故障而重新安排
- django - 在 DRF 中删除与 PrimaryKeyRelatedField 的多对多关系
- pandas - 与熊猫系列按键分组并导出 to_dict()
- elasticsearch - Elasticsearch 在列表字段中查找唯一项目
- google-apps-script - 谷歌应用脚本超出内存限制
- swift - 点击单元格后在第二个 UI 集合视图中加载特定项目?
- gradle - Kotlin 添加集成测试模块
- pyqt - Pyqt5 两个 QThread 通信使用信号和槽问题
- java - 只有有限的 mqtt 客户端连接到 IBM MQ 代理