在 React 中使用 Hooks 实现倒数计时器
Posted
技术标签:
【中文标题】在 React 中使用 Hooks 实现倒数计时器【英文标题】:Implementing a countdown timer in React with Hooks 【发布时间】:2019-11-29 21:39:47 【问题描述】:我正在尝试使用反应挂钩在屏幕上呈现倒计时计时器,但我不确定呈现它的最佳方式。
我知道我应该使用 useEffect 来比较当前状态和以前的状态,但我认为我做得不对。
非常感谢您的帮助!
我尝试了几种不同的方法,但它们都不起作用,例如每当更新时设置状态,但它最终会像疯了一样闪烁。
const Timer = ( seconds ) =>
const [timeLeft, setTimeLeft] = useState('');
const now = Date.now();
const then = now + seconds * 1000;
const countDown = setInterval(() =>
const secondsLeft = Math.round((then - Date.now()) / 1000);
if(secondsLeft <= 0)
clearInterval(countDown);
console.log('done!');
return;
displayTimeLeft(secondsLeft);
, 1000);
const displayTimeLeft = seconds =>
let minutesLeft = Math.floor(seconds/60) ;
let secondsLeft = seconds % 60;
minutesLeft = minutesLeft.toString().length === 1 ? "0" + minutesLeft : minutesLeft;
secondsLeft = secondsLeft.toString().length === 1 ? "0" + secondsLeft : secondsLeft;
return `$minutesLeft:$secondsLeft`;
useEffect(() =>
setInterval(() =>
setTimeLeft(displayTimeLeft(seconds));
, 1000);
, [seconds])
return (
<div><h1>timeLeft</h1></div>
)
export default Timer;```
【问题讨论】:
钩子和间隔的经典问题。看看overreacted.io/making-setinterval-declarative-with-react-hooks 并注意在拆机时取消间隔 【参考方案1】:const Timer = ( seconds ) =>
// initialize timeLeft with the seconds prop
const [timeLeft, setTimeLeft] = useState(seconds);
useEffect(() =>
// exit early when we reach 0
if (!timeLeft) return;
// save intervalId to clear the interval when the
// component re-renders
const intervalId = setInterval(() =>
setTimeLeft(timeLeft - 1);
, 1000);
// clear interval on re-render to avoid memory leaks
return () => clearInterval(intervalId);
// add timeLeft as a dependency to re-rerun the effect
// when we update it
, [timeLeft]);
return (
<div>
<h1>timeLeft</h1>
</div>
);
;
【讨论】:
在这里使用 setTimer 是否更有意义? @AmirShitrit 你的意思是setTimeout
?两者都是有效的选项,但setTimeout
假设触发一次函数,而setInterval
假设每 x 时间触发一次函数。由于useEffect
的性质,我们需要在每次timeLeft
更改时设置和清除计时器,我猜它的行为并不像“真实”setInterval
,我可以在setTimeout
中看到你的观点这个案例。
是的。我的意思是设置超时。谢谢!
@AmirShitrit 是的,我更喜欢 setTimeout
我最喜欢它的地方是它可以工作。【参考方案2】:
您应该使用setInterval
。我只是想对@Asaf 解决方案进行一点改进。 您不必每次更改值时都重新设置间隔。它会删除间隔并每次添加一个新间隔(在这种情况下最好使用setTimeout
)。所以你可以删除你的useEffect
(即[]
)的依赖:
function Countdown( seconds )
const [timeLeft, setTimeLeft] = useState(seconds);
useEffect(() =>
const intervalId = setInterval(() =>
setTimeLeft((t) => t - 1);
, 1000);
return () => clearInterval(intervalId);
, []);
return <div>timeLefts</div>;
工作示例:
注意在setter中,我们需要使用(t) => t - 1
这样的语法,这样我们每次都能得到最新的值(见:https://reactjs.org/docs/hooks-reference.html#functional-updates)。
编辑(2021 年 10 月 22 日)
如果您想使用 setInterval
并将计数器停止在 0,您可以执行以下操作:
function Countdown( seconds )
const [timeLeft, setTimeLeft] = useState(seconds);
const intervalRef = useRef(); // Add a ref to store the interval id
useEffect(() =>
intervalRef.current = setInterval(() =>
setTimeLeft((t) => t - 1);
, 1000);
return () => clearInterval(intervalRef.current);
, []);
// Add a listener to `timeLeft`
useEffect(() =>
if (timeLeft <= 0)
clearInterval(intervalRef.current);
, [timeLeft]);
return <div>timeLefts</div>;
【讨论】:
区间将无限运行 如果您想在 0 处停止,您可以通过卸载组件来停止计数器:seconds > 0 && <Countdown seconds=seconds />
。但确实,根据要求,它可能需要一些调整。如果您更新道具中的seconds
,它也不会更新间隔值。我只是想提供一个有效的替代setTimeout
,使用setInterval
(而不是在每次渲染时重置它)。
就是这样,父母对timeLeft
一无所知,更新每次重新渲染的间隔非常好,并且当timeLeft
达到某个点时,您可以选择做事像 0。你可以在状态更新回调中做到这一点,但在我看来这很难看
ESLint 有一个规则 react-hooks/exhaustive-deps
强制在数组中添加 timeLeft
的依赖项。但是,仍然同意不必每次都清除间隔。我认为在这种情况下我将不得不使用setTimeout
【参考方案3】:
这是 setTimeout 的另一种选择
const useCountDown = (start) =>
const [counter, setCounter] = useState(start);
useEffect(() =>
if (counter === 0)
return;
setTimeout(() =>
setCounter(counter - 1);
, 1000);
, [counter]);
return counter;
;
例子
【讨论】:
以上是关于在 React 中使用 Hooks 实现倒数计时器的主要内容,如果未能解决你的问题,请参考以下文章
ZF_react hooks useEffect的实现 useRef useImperativeHandle的实现,react整体功能实现完毕