背景:
实现答题倒计时的功能:设置答题时间例如10s,用户需要在答题时间内作答,超出答题时间未答,提示用户超时。
定时器的创建和销毁:
定时器创建时期:
页面载入时
首先在函数内定义一个定时器interval,页面加载时开始计时
const QuestionPage: React.FC<IQuestionPage> = (props) => {
let remainTime = 0;
let interval: any = null;
useEffect(() => {
interval = setInterval(() => {
if (remainTime <= 0) {
clearInterval(interval)
} else {
remainTime--
}
}, 1000)
}, [])
const chooseAnswer = () => {
clearInterval(interval)
}
return <div onClick={chooseAnswer}>点击销毁定时器</div>
}
export default QuestionPage
定时器需要销毁时期:
1、页面即将销毁
useEffect(() => {
return () => {
if (interval) {
clearInterval(interval)
}
}
}, [])
2、倒计时结束
第一个代码块已展示
3、在答题时间内用户点击答案
const chooseAnswer = () => {
clearInterval(interval)
}
遇到的问题:
倒计时结束,定时器可销毁,但是用户在答题时间内作答,点击销毁定时器未生效,依然在倒计时
解决方法1:
将定时器放在函数的最外层
let remainTime = 0;
let interval: any = null;
const QuestionPage: React.FC<IQuestionPage> = (props) => {
。。。
}
解决方法2:
将定时器放在状态里
const QuestionPage: React.FC<IQuestionPage> = (props) => {
const [interval, setinterval] = React.useState(null);
useEffect(() => {
let interval = setInterval(() => {
if (remainTime <= 0) {
clearInterval(interval)
} else {
remainTime--
}
}, 1000)
setinterval(interval)
}, [])
const chooseAnswer = () => {
clearInterval(interval)
}
return <div onClick={chooseAnswer}>点击销毁定时器</div>
}
export default QuestionPage
总结:
react用链表的方式来存储函数式组件里面的hooks,并为每一个hook创建了一个对象,在组件更新时会根据链表拿到当前hooks对应的hook对象。
在例子中用useEffect对定时器倒计时,在初始化时渲染一次,此时定时器内回调函数内会形成闭包,倒计时结束后清除定时器会生效。但是在后续外部调用清除定时器的方法时,页面可能已重新渲染多次,interval的值早已被重置。
方法1把定时器设为全局变量,interval不会被重新赋值,所以生效,但是这种方法不推荐,变量放在外部可能会引起冲突。方法2将定时器放进useState,会跟每次渲染一起将状态向后续的hook链表传递,状态被保存了下来,推荐此方法。