簡體   English   中英

為什么 setInterval 在沒有'useEffect'的情況下不能在 React 中工作?

[英]WHY doesn't setInterval works in React without 'useEffect'?

所以,我正在學習 React,在這里真的很新。 簡短的問題確實在標題中,為什么?

在進行倒計時的練習中,我嘗試自己制作 function,然后再查看完整的視頻,它使用useEffect

這是我的代碼:

...
function countdown(){
  setInterval(()=>{
    setTime(time-1) // an useState var
  }, 1000)
}
    
...
<button onclick={countdown}

在 React 上中繼以“響應”變量更改值,將其渲染出來。

非常簡單,就像在這個工作 vanillaJS 腳本中一樣:(注意運行時的無限“循環”)

 let secs = 10; const counter = document.querySelector('#counter'); counter.innerText = secs; function countdown() { setInterval(() => { counter.innerText = --secs; }, 1000) }
 <button onclick = countdown() >countdown</button> <p><span id='counter'>#</span>sec</p>


這里出乎意料的是,在 React 版本中,它變得瘋狂:

  • 頁面上的計數器元素僅下降一次。
  • 從 setInterval 中記錄var ,總是吐出相同的值。
  • 從外部記錄,確實顯示 var 低一秒。
  • 最令人難以置信的是,多次單擊使其 go 閃爍 9,8,7,(...),9,7,8,(...)

直接進行點擊倒計時,每次交互都表現得很好。

因此,假設它與異步有關,不知何故 Interval 不允許 useState 更新值,並且可能某種將 Interval 封閉在上下文中,每個上下文都為相同的 var 名稱設置不同的值。

最后一個問題:為什么是[fork]?? 當然 useEffect 在這里要好得多,但它看起來真的像 React 為自己引起的問題“出售”了一個解決方案。 與 JS 相比,我在 React 中看到了如此多的“官僚代碼”,這讓我感覺像是擺脫了一些高級/抽象,轉而使用更像 C/Java 的低級。 這是一個很好的例子。

所以我真的很感興趣,React 中的簡單計數器變得如此困難的充分理由是什么?

要回答您關於“為什么”的問題,我強烈建議您閱讀 Dan Abramov https://overreacted.io/making-setinterval-declarative-with-react-hooks/ 撰寫的這篇文章

它涉及您所要求的確切細節。

至於您的意外行為,您的倒計時設置了 state,它重新呈現頁面,將secs的值重新啟動為10 ,然后您的間隔再次運行,將值降至9導致 state 更改,它重新呈現頁面,你是陷入了瘋狂的循環。

我不確定為什么點擊會改變事情,我唯一的猜測是點擊可能會延遲重新渲染,你實際上會看到一些變化。

但是,“為什么”的答案和問題的解決方案都可以在這篇精彩的文章中找到。

在您的第一個片段中,計時器只減 1 的原因與 React 無關,而是由於 Javascript 本身的基本特性。 在您的代碼中,傳遞給setInterval的 function 是

()=>{
    setTime(time-1) // an useState var
 }

在你的上下文中,這里的time是一段 React state - 但這並不重要。 它是一個變量,持有一個值——在你的例子中是數字 10。實際上,這會在聲明time的外部 scope 上創建所謂的“閉包”——在 React function 組件中將是該組件 ZC1C425264D17835 本身。 這種“關閉”變量可能會在 function 調用之間改變值——但這不會發生在 React 組件中,因為 React 依賴於不變性。 如果你真的這樣寫:

()=>{
    time--;
    console.log(time);
    setTime(time);
 }

然后您在控制台中看到正在減少。 但我並不是建議你真的這樣做,因為組件肯定會以某種方式破壞 - state 變量,就像 props 一樣,永遠不應該在組件內更改或變異,否則 React 可能不會意識到它需要重新渲染組件.

So instead, React gives you a function that takes care of updating the state - this.setState in a class component, or the function returned by useState in a function (this is your setTime ). Whether the component is a function or a class doesn't make any difference, in both cases the function tells React that the state should change, and therefore schedules another render of your component, with the new state. 在您使用 Hook 的示例中,這意味着再次調用 function(= 組件),但相關的useState調用將返回time變量的更新值。 (如果你願意,你可以把它當作“反應魔法”不屑一顧,但既然我在談論閉包,我應該指出,“魔法”真的又是閉包

簡而言之,非工作示例的問題在於, time實際上從未從它在特定的第一個渲染上的值更新。 function 和 class 組件之間的工作方式完全不同,但通常是功能的優勢

請注意,您可以通過使用function 參數形式在此處“修復”代碼,並避免“陳舊關閉”問題:

()=>{
    setTime(time => time-1)
 }

但是,如果您多次單擊該按鈕,這仍然會導致“瘋狂”、不可預測的結果。 而且您還會導致 memory 泄漏,因為 function 仍會每秒調用一次(每次用戶單擊一次。)即使在卸載組件時也是如此。

這正是useEffect的用途——允許在每次渲染時發生一些“有效的功能”(這里是計時器減少)(假設相關的東西已經改變),並且還允許你清理它。 如果您只想讓計時器每秒倒計時,而與任何用戶操作無關,您只需執行以下操作:

useEffect(() => {
    const interval = setInterval(() => setTime(time => time - 1), 1000);
    return () => clearInterval(interval);
}, []);

在您的實際示例中,倒計時僅應在單擊按鈕時開始,並且您可能不希望它在第二次單擊時以兩倍的速度開始倒計時,您可以使用 Boolean state 變量來指示計時器是否應該正在運行,並在調用setTime之前檢查setInterval回調內部。 然后您的onclick將打開該變量。 這樣做應該會給你你想要的行為。

與在香草 JS 中做同樣的事情相比,這是相當多的樣板嗎? 是的。 如果你有一個小規模的項目,那么使用 React 之類的東西可能沒有意義。 對於您構建的每個項目,這是一個判斷和個人意見的問題。 但是,能夠編寫聲明式組件而不是像使用 vanilla(或 jQuery)那樣編寫命令式代碼有相當大的優勢,尤其是在大型應用程序中。 當然,React 並不是唯一的聲明式框架——每個開發人員和每個團隊都必須自己決定使用什么。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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