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值。
如果我們把 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。
以上只是個人的理解,可能有不正確的地方,歡迎指正,謝謝~