用函數閉包的思想去解釋 Hooks 的 Capture Value 特性


useEffect 完整指南 中談到過:

Effect拿到的總是定義它的那次渲染中的props和state。這能夠避免一些bugs,但在一些場景中又會有些討人嫌。對於這些場景,你可以明確地使用可變的ref保存一些值。

我們先來看一個 Capture Value 的例子:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      console.log(`You clicked ${count} times`);
    }, 3000);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

如果我點擊了很多次並且在effect里設置了延時,打印出來的結果會是什么呢?

結果並不會是我們想的和類組件一樣輸出多個最新的 count 值。而是順序的打印輸出 — 每一個都屬於某次特定的渲染,因此有它該有的count值。

image

如果我們把 Hooks 函數組件當成普通的函數,從函數閉包的角度就能很好的理解這一特性了。

let _state = 0;

function useState(initialValue) {
  _state = _state | initialValue;
  function setState(newState) {
    _state = newState;
    // 會重新執行組件函數
    // render();
  }
  return [_state, setState];
}

function Component() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setTimeout(() => {
      console.log("You clicked on: " + count);
    }, 3000);
  };
  // 暴露頁面上可以執行的函數
  return [handleClick, setCount];
}

// 初次 render
const [handleClick, setCount] = Component();

// 模擬點擊按鈕
handleClick();
setCount(1); // 此時會重新執行 Component(),並返回新的 handleClick
setCount(2);

先理解一下閉包的一些概念:
函數和對其周圍狀態(lexical environment,詞法環境)的引用捆綁在一起構成閉包(closure)。
也就是說,閉包可以讓你從內部函數訪問外部函數作用域。
在 JavaScript 中,每當函數被創建,就會在函數生成時生成閉包。

每次 render ,函數組件重新執行,生成並返回了和當前詞法環境綁定的函數,函數組件閉包詞法環境中的 state,props 是不會改變的。
當 handleClick() 執行時,輸出的必然是初次 render 是的 count ,即 0 。3 秒之內無論多少次 setCount(),改變 count 的值,也只是每次 setCount() 后重新執行組件函數的新的詞法環境的 count 值被改變了,並返回了新的 handleClick,並不會影響到第一次 render 閉包詞法環境中的 count 。

所以打印結果是 You clicked on: 0

從而也解釋了為什么 每一次渲染都有它自己的 Props、State and Effects,每一個組件內的函數(包括事件處理函數,effects,定時器或者API調用等等)會捕獲某次渲染中定義的props和state

以上只是個人的理解,可能有不正確的地方,歡迎指正,謝謝~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM