前言
最近在做一个 H5 工具,需要手机号 + 验证码登录,很自然地,点击发送验证码后需要等待一段时间才能重新发送,用于请求节流,避免用户疯狂点击:
不过这里其实有个隐藏需求——如果仍然在冷却时间内,那么用户无论是刷新或是关闭页面,再次打开登录弹窗,需要直接展示正确的倒计时状态。
解决方案
使用经典的 localStorage:
· 发送验证码时,将发送时间 (lastSendingTime) 存入 localStorage,并开启 60 秒倒计时。
· 倒计时结束后,清除 localStorage 中的 lastSendingTime。
· 重新进入页面时,若 localStorage 中存有 lastSendingTime,则说明仍处于冷却时间内,那么计算出剩余的倒计时 N,并开启 N 秒倒计时。
Talk is cheap, show me the code!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| const [countdown, setCountdown] = useState(60) const [canSendCode, setCanSendCode] = useState(true) const [timer, setTimer] = useState()
async function sendVerificationCode() { try { Toast.show({ content: '验证码发送成功' }) startCountdown() setCanSendCode(false) } catch (error) { setCountdown(0) setCanSendCode(true) } }
function startCountdown() { const nowTime = new Date().getTime() const lastSendingTime = localStorage.getItem('lastSendingTime') if (lastSendingTime) { const restCountdown = 60 - parseInt(((nowTime - lastSendingTime) / 1000), 10) setCountdown(restCountdown <= 0 ? 0 : restCountdown) } else { setCountdown(60) localStorage.setItem('lastSendingTime', nowTime) }
setTimer( setInterval(() => { setCountdown(old => old - 1) }, 1000), ) }
useEffect(() => { const lastSendingTime = localStorage.getItem('lastSendingTime') if (lastSendingTime) { setCanSendCode(false) startCountdown() }
return () => { clearInterval(timer) } }, [])
useEffect(() => { if (countdown <= 0) { setCanSendCode(true) localStorage.removeItem('lastSendingTime') clearInterval(timer) setCountdown(60) } }, [countdown])
return ( {canSendCode ? ( <span onClick={sendVerificationCode}> 获取验证码 </span> ) : ( <span> 获取验证码({`${countdown}`}) </span> )} )
|
最终效果
总结
一开始感觉这是个很简单的小需求,可能 20min 就写完了,但实际花了两个多小时才把逻辑全部 cover 到,还是不能太自信啊~
留言
欢迎交流想法,也欢迎指出文中的问题。