setInterval + React 钩子导致组件内的多次更新 [英] setInterval + React hooks causing multiple updates within component
问题描述
我正在构建一个秒表 UI,以秒为单位显示时间.单击按钮,计时器将开始向上计数,并在再次单击时停止.用户应该能够再次启动它.
我遇到的问题是我可以让 setInterval
正常工作,但是一旦我包含了 setTime
钩子,组件就会更新以在 UI 中呈现时间,但是setInterval
实例被多次调用.这会导致奇怪的渲染行为.
const Timer = () =>{const [时间,设置时间] = useState(0)让计时器const startStopTimer = () =>{if (!timer) timer = setInterval(() => setTime(time++), 1000)别的 {清除间隔(定时器)计时器 = 空}}返回 (<div><p>时间:{time} 秒</p><按钮onClick={() =>{开始停止定时器()}>开始/停止</按钮>
)}
示例行为是:
- 用户点击开始/停止
- 计时器从 0 开始向上计数
- 用户点击开始/停止
- 计时器立即停止
- 用户点击开始/停止
- 计时器从停止的地方继续
这是 React 钩子中陈旧闭包的经典示例,在调用 后,
.更改您的代码:time
的 setInterval 值不会改变设置时间
setInterval(() => setTime(currentTime => currentTime + 1), 1000)
.
setTime
就像有类组件的 setState
也接受一个回调函数,它以当前值作为第一个参数
此外,timer
变量在您的代码中是无用的,因为在每次重新渲染时它都将是未定义的,您将无法访问 setInterval
的返回值,所以它会重新初始化setInterval
.为了处理 useRef
的使用,您可以将 setInterval
的返回存储在 .current
中,在后续重新渲染后即可使用,因此无需更多重新初始化 setInterval 并且您也可以使用 clearInterval
解决方案:
const {useState, useRef} = React;const {render} = ReactDOM;const 定时器 = () =>{const [time, setTime] = useState(0);const timer = useRef(null);const startStopTimer = () =>{如果(!定时器.当前){timer.current = setInterval(() => setTime(currentTime => currentTime + 1), 1000);} 别的 {clearInterval(timer.current);timer.current = null;}};返回 (<div><p>时间:{time} 秒</p><按钮onClick={startStopTimer}>开始/停止按钮>
);};render(
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></脚本><script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script><div id="root"></div>
I'm building a stopwatch UI that shows the time in seconds. With the click of a button, the timer will start counting upwards and stop when it is clicked again. User should be able to start it again.
The issue I'm having is that I can have setInterval
working correctly but once I include setTime
hook, the component updates to render the time in the UI but the setInterval
instance is being called multiple times. This leads to odd rendering behavior.
const Timer = () => {
const [time, setTime] = useState(0)
let timer
const startStopTimer = () => {
if (!timer) timer = setInterval(() => setTime(time++), 1000)
else {
clearInterval(timer)
timer = null
}
}
return (
<div>
<p>Time: {time} seconds</p>
<Button
onClick={() => {
startStopTimer()
}
> Start/Stop </Button>
</div>
)
}
Example behavior would be:
- User clicks Start/Stop
- Timer starts from 0 and counts upward
- User clicks Start/Stop
- Timer stops immediately
- User clicks Start/Stop
- Timer continues where it left off
This is a classic example of stale closure in React hooks, inside your setInterval value of time
is not changing after calling setTime
. Change your code with:
setInterval(() => setTime(currentTime => currentTime + 1), 1000)
.
setTime
just like the setState
of classful components also accepts a callback function which has the current value as the first param
Also, the timer
variable is useless in you code since on every re-render it will be undefined and you wont't have the access of return value of setInterval
, so it will reinitialize the setInterval
. To handle that use useRef
, you can store the return of setInterval
in .current
, which will be available to you after subsequent re renders so no more re-init of setInterval and you can also use clearInterval
Solution:
const {useState, useRef} = React;
const {render} = ReactDOM;
const Timer = () => {
const [time, setTime] = useState(0);
const timer = useRef(null);
const startStopTimer = () => {
if (!timer.current) {
timer.current = setInterval(() => setTime(currentTime => currentTime + 1), 1000);
} else {
clearInterval(timer.current);
timer.current = null;
}
};
return (
<div>
<p>Time: {time} seconds</p>
<button
onClick={startStopTimer}
>
Start/Stop
</button>
</div>
);
};
render(<Timer />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
这篇关于setInterval + React 钩子导致组件内的多次更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!