首頁  >  文章  >  web前端  >  React:陳舊的關閉

React:陳舊的關閉

王林
王林原創
2024-08-21 06:19:02450瀏覽

在這篇文章中,我將展示如何在 useState hook React 應用程式中建立閉包。

我不會解釋什麼是閉包,因為關於這個主題的資源有很多,我不想重複。我建議閱讀 @imranabdulmalik 的這篇文章。

簡而言之,閉包(來自 Mozilla):

...捆綁在一起(封閉)的函數及其周圍狀態(詞法環境)的引用的組合。換句話說,閉包使您可以從內部函數存取外部函數的作用域。在 JavaScript 中,每次建立函數時都會建立閉包,在函數建立時.

萬一您不熟悉術語詞彙環境,您可以閱讀@soumyadey 的這篇文章或這篇文章。

問題

在 React 應用程式中,您可能會意外建立屬於使用 useState 掛鉤建立的元件狀態的變數的閉包。發生這種情況時,您將面臨陳舊閉包問題,也就是說,當您引用狀態的舊值時,它同時已更改,因此它不再相關。

POC

我創建了一個 Demo React 應用程序,其主要目標是增加一個計數器(屬於狀態),該計數器可以在 setTimeout 方法的回調中的閉包中關閉。

簡而言之,這個應用程式可以:

  • 顯示計數器的值
  • 計數器加 1
  • 啟動計時器,在五秒後將計數器加 1。
  • 計數器加 10

下圖中,顯示了應用程式的初始 UI 狀態,計數器為零。

React: stale closure

我們將分三步驟模擬櫃檯關閉

  1. 計數器加 1

React: stale closure

  1. 啟動計時器,五秒後加 1

React: stale closure

  • 超時觸發前增加 10 React: stale closure

5秒後,計數器的值為2。

React: stale closure

計數器的預期值應該是12,但我們得到2

發生這種情況的原因是因為我們在傳遞給setTimeout的回調中創建了計數器的閉包,並且當觸發超時時,我們從其開始設定計數器舊值(即1)。

setTimeout(() => {
        setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`])
        setTimeoutInProgress(false)
        setStartTimeout(false)
        setCounter(counter + 1)
        setLogs((l) => [...l, `Did you create a closure of counter?`])

      }, timeOutInSeconds * 1000);

遵循應用程式元件的完整程式碼。

function App() {
  const [counter, setCounter] = useState(0)
  const timeOutInSeconds: number = 5
  const [startTimeout, setStartTimeout] = useState(false)
  const [timeoutInProgress, setTimeoutInProgress] = useState(false)
  const [logs, setLogs] = useState>([])

  useEffect(() => {
    if (startTimeout && !timeoutInProgress) {
      setTimeoutInProgress(true)
      setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`])
      setTimeout(() => {
        setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`])
        setTimeoutInProgress(false)
        setStartTimeout(false)
        setCounter(counter + 1)
        setLogs((l) => [...l, `Did you create a closure of counter?`])

      }, timeOutInSeconds * 1000);
    }
  }, [counter, startTimeout, timeoutInProgress])

  function renderLogs(): React.ReactNode {
    const listItems = logs.map((log, index) =>
      
  • {log}
  • ); return
      {listItems}
    ; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (

    Closure demo


    Counter value: {counter}


    Follow the istructions to create a closure of the state variable counter

    1. Set the counter to preferred value
    2. Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
    3. Increment by 10 the counter before the timeout

    { renderLogs() }
    ); } export default App;

    解決方案

    該解決方案基於 useRef 鉤子的使用,它允許您引用渲染不需要的值。

    所以我們加入App元件:

    const currentCounter = useRef(counter)
    

    然後我們將修改setTimeout的回調,如下所示:

    setTimeout(() => {
            setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`])
            setTimeoutInProgress(false)
            setStartTimeout(false)
            setCounter(currentCounter.current + 1)
            setLogs((l) => [...l, `Did you create a closure of counter?`])
    
          }, timeOutInSeconds * 1000);
    

    我們的回調需要讀取計數器值,因為我們在增加它之前記錄了當前值。

    如果您不需要讀取值,只需使用功能符號更新計數器即可避免計數器關閉。

    seCounter(c => c + 1)
    

    資源

    • Dmitri Pavlutin 使用 React Hooks 時要注意過時的閉包
    • Imran Abdulmalik 掌握 JavaScript 中的閉包:綜合指南
    • JavaScript 中的 Keyur Paralkar 詞法範圍 – 初學者指南
    • React 中的 Souvik Paul Stale 閉包
    • Soumya Dey 理解 JavaScript 中的詞法範圍與閉包
    • Subash Mahapatra stackoverflow

    以上是React:陳舊的關閉的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn