我编写了一个自定义的 React
useCountDown
钩子来倒数一个数字(秒)直到零。计数由按钮 start()
侦听器中附加的 onClick
方法触发。
没有
<React.StrictMode>
也能正常工作,但在严格模式下就不行了。
import { useCallback, useEffect, useState } from "react";
export default function useCountDown(countdownSeconds = 10) {
const [timing, setTiming] = useState(false);
const [seconds, setSeconds] = useState(countdownSeconds);
const reset = useCallback(() => {
setTiming(true);
setSeconds(countdownSeconds);
}, [countdownSeconds]);
const start = useCallback(() => setTiming(true), []);
useEffect(() => {
let timer: number | undefined;
function countdown() {
setSeconds((preSecond) => {
if (preSecond <= 1) {
setTiming(false);
return countdownSeconds;
} else {
timer = setTimeout(countdown, 1000);
return preSecond - 1;
}
});
}
if (timing) {
timer = setTimeout(countdown, 1000);
}
return () => {
clearTimeout(timer);
};
}, [timing, countdownSeconds]);
return {
start,
seconds,
timing,
reset,
};
}
严格模式下,计数快速减零,不间隔(1s)且永不停止计数。但是,当我删除
<React.StrictMode>
时,它运行得很好。
我发现
countdown()
执行了太多次,没有间隔。代码有什么问题吗?我该如何解决这个问题?
我知道另一种方法将在严格模式下工作,如下所示。但我试图找出上述方法出了什么问题。
useEffect(() => {
let timer: NodeJS.Timeout
if (timing) {
timer = setTimeout(() => {
if (seconds > 0) {
setSeconds(seconds - 1)
} else {
onCountdownEndRef.current?.()
setSeconds(countdownSeconds)
setTiming(false)
}
}, 1000)
}
return () => clearTimeout(timer)
}, [seconds, timing, countdownSeconds])
======编辑=====
即使我将
setTiming(false)
从更新程序功能中移出,它也不起作用。
useEffect(() => {
seconds === 0 && setTiming(false);
}, [seconds]);
useEffect(() => {
let timer: number | undefined;
function countdown() {
setSeconds((preSecond) => {
if (preSecond <= 1) {
return countdownSeconds;
} else {
timer = setTimeout(countdown, 1000);
return preSecond - 1;
}
});
}
if (timing) {
timer = setTimeout(countdown, 1000);
}
return () => {
clearTimeout(timer);
};
}, [timing, countdownSeconds]);
使用refs不是更方便吗?
export default function useCountDown(countdownSeconds = 10) {
const seconds = useRef(countdownSeconds);
const interval = useRef();
const [, update] = useReducer((x) => !x, 0);
const reset = useCallback(() => {
seconds.current = countdownSeconds;
update();
}, [countdownSeconds]);
const start = useCallback(() => {
interval.current = setInterval(() => {
if (seconds.current < 1) {
clearInterval(interval.current);
return;
}
seconds.current -= 1;
update();
}, 1000);
}, []);
return {
start,
seconds: seconds.current,
reset
};
}