在react中使用state实现秒倒数时出现错误



我有以下的react组件,我试图开始倒计时从30到0,如下所示:

import React from 'react';
function Countdown () {
const [time, setTime] = React.useState('');
const [start, setStart] = React.useState(false);
const startTimer = () => {
setStart(true)
setTime('30')
}
const startTiming = setInterval(() => {
if (start) {
if (time !== '0') {
setTime((parseInt(time) - 1).toString());
} else {
clearInterval(startTiming)
setStart(false);
}
}
}, 1000)
return (<>
{ start ? time : 'wait to start' }
<button onClick={startTimer}>Start Timer</button>
</>)
}
export default Countdown;

我想通过点击按钮从30秒开始倒计时。然而,一旦倒计时到23秒左右,它就不再持续地倒数1秒,而是似乎进入了一个无限循环——有人知道这是为什么吗?理想情况下,我想通过使用start状态来实现这一点。

我建议在用户单击Start时使用useTimer(hz)钩子捕获offset。这样elapsed时间总是正确的,不会因为不完美的超时delta而漂移。钩子返回[elapsed, { start, stop }],它是具有浮点精度和startstop控件的总运行时间。可选的hz参数控制定时器更新的频率。

Countdown组件具有seconds以及可配置的hz属性。通过从seconds中减去elapsed的时间,我们知道还剩下多少时间。

运行下面的程序,点击启动每个定时器,不要忘记有一个好时间!

function useTimer(hz = 100) {
const [[offset, now], setState] = React.useState([null, null])
const start = React.useCallback(_ => setState([Date.now(), Date.now()]))
const stop = React.useCallback(_ => setState([null, null]))
React.useEffect(_ => {
if (offset == null) return
const t = setTimeout(_ => { setState([offset, Date.now()]) }, 1000 / hz)
return _ => clearTimeout(t)
}, [offset, now])
return [offset == null ? 0 : (now - offset) / 1000, {start, stop}]
}

function Countdown({ seconds, hz = 1 }) {
const [elapsed, { start, stop }] = useTimer(hz)
React.useEffect(_ => { if (elapsed > seconds) stop() }, [elapsed, stop])
return <div>
<pre>{`<Countdown seconds={${seconds}} hz={${hz}} />`}</pre>
{ elapsed > seconds
? 0
: (seconds - elapsed).toFixed(2)
}
{ elapsed > 0
? <button type="button" onClick={stop} children="Reset" />
: <button type="button" onClick={start} children="Start" />
}
</div>
}
ReactDOM.render([
<Countdown seconds={30} />,
<Countdown seconds={20} hz={10} />,
<Countdown seconds={10} hz={100} />
], document.querySelector("#app"))
body { font-family: monospace; }
pre { background-color: #ffe; padding: 0.25rem 0; margin: 0; }
p { margin: 0; }
div { margin: 0 0 1rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

最新更新