问题描述
点击按钮获取手机验证码,按钮置灰,出现60s倒计时,倒计时结束按钮恢复正常。
分析:这是一个很常见且并不复杂的功能,在vue和react类组件中我们通常会使用setInterval
来实现,以下以vue2为例:
export default {
……
methods: {
startCountdown() {
if (this.countdown === 0) {
this.countdown = 60;
this.timer = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(this.timer);
}
}, 1000);
}
}
},
……
};
但是在react函数组件中,尝试用相同的方法,却实现不了此功能。这是因为回调函数中的状态值在每次渲染时并不会自动更新
。以下是错误示例:
import React, { useState, useEffect } from 'react';
const CountdownTimer = () => {
const [count, setCount] = useState(60);
useEffect(() => {
const interval = setInterval(() => {
setCount(count - 1);
}, 1000);
// 清除定时器
return () => clearInterval(interval);
}, []);
return <div>Count: {count}</div>;
};
export default CountdownTimer;
在这个例子中,setInterval
回调函数中的 count
值为初始值 60
,并且不会随渲染更新。因此,count
会一直减到 59
,然后保持不变。
解决方法
一、使用useRef
保存最新的状态
import React, { useState, useEffect, useRef } from 'react';
const CountdownTimer = () => {
const [count, setCount] = useState(60);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
useEffect(() => {
const interval = setInterval(() => {
if (countRef.current > 0) {
setCount(countRef.current - 1);
} else {
clearInterval(interval);
}
}, 1000);
// 清除定时器
return () => clearInterval(interval);
}, []);
return <div>Count: {count}</div>;
};
export default CountdownTimer;
二、使用setTimeout
结合usEffect
,代替setInterval
(🌟推荐🌟)
import React, { useState, useEffect, useRef } from 'react';
import { Button } from 'antd'
const CountdownTimer = () => {
const [codeBtnDisabled, setCodeBtnDisabled] = useState(false)
const [countDown, setCountDown] = useState(30)
let timerId: number| null = null
useEffect( ()=> {
if (codeBtnDisabled) {
timerId = setTimeout(() => setCountDown(countDown - 1), 1000)
}
if (countDown <= 0){
setCodeBtnDisabled(false)
setCountDown(30)
}
return () => {
if (timerId) clearTimeout(timerId)
}
},[countDown, codeBtnDisabled])
return <Button disabled={ codeBtnDisabled }>
{ codeBtnDisabled ? `${count}s` : '获取手机验证码‘}
</Button>;
};
export default CountdownTimer;