簡體   English   中英

setInterval + React 鈎子導致組件內的多次更新

[英]setInterval + React hooks causing multiple updates within component

我正在構建一個秒表 UI,以秒為單位顯示時間。 單擊按鈕,計時器將開始向上計數,並在再次單擊時停止。 用戶應該能夠再次啟動它。

我遇到的問題是我可以讓setInterval正常工作,但是一旦我包含setTime掛鈎,組件就會更新以在 UI 中呈現時間,但setInterval實例被多次調用。 這會導致奇怪的渲染行為。

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>
           )
}

示例行為是:

  1. 用戶單擊開始/停止
  2. 定時器從 0 開始向上計數
  3. 用戶單擊開始/停止
  4. 定時器立即停止
  5. 用戶單擊開始/停止
  6. 計時器從停止的地方繼續

這是 React 鈎子中陳舊閉包的經典示例,在調用setTime后,您的 setInterval time值不會改變。 更改您的代碼:

setInterval(() => setTime(currentTime => currentTime + 1), 1000)

setTime就像有類組件的setState一樣,也接受一個回調 function ,它的第一個參數是當前值

此外, timer變量在您的代碼中是無用的,因為在每次重新渲染時它將是未定義的,並且您將無法訪問setInterval的返回值,因此它將重新初始化setInterval 要處理使用useRef ,您可以將setInterval的返回值存儲在.current中,在后續重新渲染后您可以使用它,因此不再需要重新初始化 setInterval ,您也可以使用clearInterval

解決方案:

 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>

這是一個使用反應 class 組件的示例。 此示例跟蹤開始時間,而不是在某個時間間隔添加某個值。 然后,當您停止計時器時,它會累積經過的時間。

傳遞給setInterval的回調可能並不總是准確地每n毫秒調用一次。 如果 JavaScript 引擎正忙,則可能需要更長的時間。 保持計數器運行的時間越長,它就會慢慢抵消實際經過的時間。

 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM