🎉
about-closure
關於閉包(closure)
直接先來看一段 JS
function outerFunction() {
let counter = 0;
function innerFunction() {
counter++;
console.log("Counter:", counter);
}
return innerFunction;
}
const myFunction = outerFunction();
myFunction(); // Counter: 1
myFunction(); // Counter: 2
myFunction(); // Counter: 3
發生了什麼
- 一開始在
outerFunction
中宣告了let counter = 0
,-
counter
是outerFunction
作用域裡的變數 - 並且執行完後變數應該會被釋放記憶體
-
- 但是因為將
innerFunction
return 出去- return 就是告訴 JS:『哎!我外面還有人要使用這個變數』
- JS 就沒有將
counter
的記憶體釋放
-
innerFuction
因為閉包的關係就擁有了使用counter
的權限- 每次執行
innerFunction
時,都可以存取並修改counter
- 每次執行
React 中常見的例子
function CounterComponent() {
const [count, setCount] = useState(0);
function handleClick() {
setTimeout(() => {
console.log('Count:', count);
setCount(count + 1);
}, 1000);
}
return <button onClick={handleClick}>Add</button>;
}
- 如果快速的點擊 3 下,會出現 3 個 0
- 因為閉包保存的是點擊當下
count
的值,也就是一開始設定的 0 - 所以就算有執行
setCount(count + 1)
,但最終還是會印出 0
Count: 0
Count: 0
Count: 0
- 有間隔的點擊確實會正確的累加,是因為 React 已經把
setCount
更新並 render 完成- 但實務上無法控制使用者手賤一直點,可能會發生以下情況
實務場景 | 影響 |
---|---|
送出表單 多次點擊「送出」按鈕 | 後端收到多次請求,產生重複資料或多次扣款! |
加購物車按鈕 快速點擊 | 同樣商品被加入多次,數量異常上升 |
切換畫面按鈕 連點 | 路由異常多次跳轉,甚至報錯 |
異步操作中重複觸發事件 | 比如 API 請求還沒完成就又點一次,結果舊的 state 被覆蓋掉 |
解法
- Functional update form of setState
- 使用
setCount((prevCount) => prevCount + 1)
const App = () => { const [, setCount] = useState(0); function handleClick() { setTimeout(() => { + setCount((prevCount) => { console.log('Count', prevCount); return prevCount + 1; }); }, 1000); } return <button onClick={handleClick}>Add</button>; };
- 使用
- 加上
disabled
狀態,按鈕送出後就先鎖定
最後に
以上為我個人的基本理解,藉由比較生活化的方式來記憶閉包的觀念,如果有什麼想法也歡迎留言跟我分享,讓我可以學習更多。
Discussion