簡體   English   中英

React Hooks useCallback 如何“凍結”閉包?

[英]How does React Hooks useCallback "freezes" the closure?

我想知道 React 如何在使用useCallback鈎子(以及其他鈎子)時“凍結”閉包,然后僅在將它們傳遞給inputs參數時更新鈎子內使用的變量。

我知道“凍結”可能不是很清楚,所以我創建了一個 REPL.it 來說明我的意思: https://repl.it/repls/RudeMintcreamShoutcast 打開代碼后,打開 web 瀏覽器控制台並開始單擊count按鈕。

對於同一個變量,外部值與內部值相比如何不同,如果它們處於相同的閉包下並引用相同的東西? 我不熟悉 React 代碼庫,所以我想我在這里遺漏了一個幕后實現細節,但我試着想了幾分鍾它是如何工作的,但無法很好地理解 React 是如何工作的實現那個。

第一次渲染組件時, useCallback鈎子會將傳遞的函數作為其參數並將其存儲在幕后。 當您調用回調時,它會調用您的函數。 到現在為止還挺好。

第二次渲染組件時, useCallback鈎子會檢查你傳入的依賴項。如果它們沒有改變,你傳入的函數將被完全忽略 當您調用回調時,它將調用您在第一次渲染時傳入的函數,該函數仍然引用該時間點的相同值。 這與您作為依賴項傳入的值無關 - 它只是普通的 JavaScript 閉包!

當依賴關系發生變化時, useCallback鈎子將接受你傳入的函數並替換它存儲的函數。 當您調用回調時,它將調用版本的函數。

所以換句話說,沒有“凍結”/有條件更新的變量——它只是存儲一個函數然后重新使用它,沒有比這更花哨的了:)

編輯:這是一個演示純 JavaScript 中發生的事情的示例:

 // React has some component-local storage that it tracks behind the scenes. // useState and useCallback both hook into this. // // Imagine there's a 'storage' variable for every instance of your // component. const storage = {}; function useState(init) { if (storage.data === undefined) { storage.data = init; } return [storage.data, (value) => storage.data = value]; } function useCallback(fn) { // The real version would check dependencies here, but since our callback // should only update on the first render, this will suffice. if (storage.callback === undefined) { storage.callback = fn; } return storage.callback; } function MyComponent() { const [data, setData] = useState(0); const callback = useCallback(() => data); // Rather than outputting DOM, we'll just log. console.log("data:", data); console.log("callback:", callback()); return { increase: () => setData(data + 1) } } let instance = MyComponent(); // Let's 'render' our component... instance.increase(); // This would trigger a re-render, so we call our component again... instance = MyComponent(); instance.increase(); // and again... instance = MyComponent();

我來到這里時對useCallback的工作方式、它與閉包的交互以及它們被它“凍結”的方式有類似的、相當模糊的不確定性。 我想通過建議查看以下設置來擴展已接受的答案,它顯示了useCallback的工作原理(出於教學原因,重要的方面是忽略 linter 的警告):

function App() {

  const [a, setA] = useState(0)

  const incrementWithUseCallback = useCallback(() => {
    // As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
    console.log(a)
    setA(a + 1)
  }, []) // but.. the linter should complain about this, saying that `a` should be included!

  const incrementWithoutUseCallback = () => {
    // This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
    console.log(a)
    setA(a + 1)
  }

  return (
    <div>
      <button onClick={incrementWithUseCallback}>Increment with useCallback</button>
      <button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
    </div>
  )
}

所以我們清楚地看到useCallback在某個時刻有效地“凍結”了它的閉包,這是一個必須理解清楚的概念,以避免混淆問題,有時也稱為“陳舊閉包”。 這篇文章可能比我更好地解釋了它: https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures

這是 Joe Clay 提供的示例代碼的另一種觀點,它強調調用回調的閉包上下文。

 //internal store for states and callbacks let Store = { data: "+", callback: null }; function functionalComponent(uniqClosureName) { const data = Store.data;//save value from store to closure variable const callback = Store.callback = Store.callback || (() => { console.log('Callback executed in ' + uniqClosureName + ' context'); return data; }); console.log("data:", data, "callback():", callback()); return { increase: () => Store.data = Store.data + "+" } } let instance = functionalComponent('First render'); instance.increase(); instance = functionalComponent('Second render'); instance.increase(); instance = functionalComponent('Third render');

如您所見,沒有依賴項的回調將始終在 useCallback 存儲它的閉包中執行,因此“凍結”閉包。

發生這種情況是因為當創建用於回調的 function 時,它僅在第一次“渲染”期間創建一次。 稍后這個 function 被重新使用,並使用第一次調用時從 Store.data 中記錄的data值。

在下一個示例中,您可以“本質上”看到閉包“凍結”邏輯。

 let globalX = 1; const f = (() => { let localX = globalX; return () => console.log(localX); } )(); globalX = 2;//does not affect localX, it is already saved in the closure f();//prints 1

暫無
暫無

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

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