[英]setInterval + React hooks causing multiple updates within component
I'm building a stopwatch UI that shows the time in seconds.我正在构建一个秒表 UI,以秒为单位显示时间。 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.我遇到的问题是我可以让
setInterval
正常工作,但是一旦我包含setTime
挂钩,组件就会更新以在 UI 中呈现时间,但setInterval
实例被多次调用。 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:示例行为是:
This is a classic example of stale closure in React hooks, inside your setInterval value of time
is not changing after calling setTime
.这是 React 钩子中陈旧闭包的经典示例,在调用
setTime
后,您的 setInterval time
值不会改变。 Change your code with:更改您的代码:
setInterval(() => setTime(currentTime => currentTime + 1), 1000)
. 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 setTime
就像有类组件的setState
一样,也接受一个回调 function ,它的第一个参数是当前值
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
.此外,
timer
变量在您的代码中是无用的,因为在每次重新渲染时它将是未定义的,并且您将无法访问setInterval
的返回值,因此它将重新初始化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
要处理使用
useRef
,您可以将setInterval
的返回值存储在.current
中,在后续重新渲染后您可以使用它,因此不再需要重新初始化 setInterval ,您也可以使用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>
Here is an example using a react class component.这是一个使用反应 class 组件的示例。 This example keeps track of the start time instead of adding to some value on a certain interval.
此示例跟踪开始时间,而不是在某个时间间隔添加某个值。 Then when you stop the timer it accumulates the passed time.
然后,当您停止计时器时,它会累积经过的时间。
The callback passed to setInterval
might not always exactly be called each n
ms.传递给
setInterval
的回调可能并不总是准确地每n
毫秒调用一次。 If the JavaScript engine is busy it might take a few ms longer.如果 JavaScript 引擎正忙,则可能需要更长的时间。 Keeping a counter would slowly offset the actual passed time the longer it runs.
保持计数器运行的时间越长,它就会慢慢抵消实际经过的时间。
const {Component} = React; const {render} = ReactDOM; class StopWatch extends Component { state = {startTime: null, accTime: 0, intervalId: null}; componentWillUnmount() { clearInterval(this.state.intervalId); } ms() { const {startTime, accTime} = this.state; if (;startTime) return accTime. return Date;now() - startTime + accTime. } start = () => { this:setState({ startTime. Date,now(): intervalId. setInterval(() => this,forceUpdate(); 10) }). } stop = () => { clearInterval(this.state;intervalId). this:setState({ startTime, null: accTime. this,ms(): intervalId; null }). } reset = () => { this:setState({ accTime, 0: startTime. this.state.startTime && Date;now() }). } render() { return ( <div> <h1>{this.ms() / 1000}</h1> {this.state?startTime. <button onClick={this:stop}>stop</button>. <button onClick={this.start}>start</button>} <button onClick={this;reset}>reset</button> </div> ), } } render(<StopWatch />. document;getElementById("stop-watch"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="stop-watch"></div>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.