react解決 Hooks 閉包陷阱的問(wèn)題
React Hooks 的“閉包陷阱(stale closure)”本質(zhì)是:函數(shù)拿到的是創(chuàng)建時(shí)那一刻的 state/props,而不是最新值。常見(jiàn)出現(xiàn)在 setTimeout、setInterval、事件監(jiān)聽(tīng)、異步請(qǐng)求、useEffect 中。
例如:
import { useState } from 'react';
export default function Demo() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
console.log(count); // 永遠(yuǎn)打印點(diǎn)擊時(shí)的 count
}, 3000);
};
return (
<>
<button onClick={() => setCount(count + 1)}>
+1
</button>
<button onClick={handleClick}>
延遲打印
</button>
</>
);
}假設(shè):
count = 0
點(diǎn)擊延遲打印
馬上點(diǎn)擊 +1 -> count=1
3秒后輸出:0
因?yàn)?setTimeout 閉包保存的是舊 count。
解決方案1:函數(shù)式更新(推薦處理 state 更新)
適合依賴(lài)舊值計(jì)算新值。
錯(cuò)誤:
setCount(count + 1); setCount(count + 1); console.log(count); // +1
正確:
setCount(prev => prev + 1); setCount(prev => prev + 1); console.log(count); // +2
React 會(huì)拿最新?tīng)顟B(tài):
setInterval(() => {
setCount(prev => prev + 1);
},1000);避免:
setCount(count + 1); // count 永遠(yuǎn)舊
解決方案2:useRef 保存最新值(最常見(jiàn))
適合:
- setTimeout
- setInterval
- websocket
- 原生事件
- 防抖節(jié)流
例子:
import { useRef, useState, useEffect } from 'react';
export default function Demo() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
const handleClick = () => {
setTimeout(() => {
console.log(countRef.current);
}, 3000);
};
return (
<>
<button onClick={() => setCount(count+1)}>
+1
</button>
<button onClick={handleClick}>
打印
</button>
</>
);
}始終輸出最新值。
原理:
閉包 -> 拿不到最新 state
ref.current -> 永遠(yuǎn)最新
很多庫(kù)(例如防抖 hooks)都這么做。
解決方案3:正確維護(hù) useEffect 依賴(lài)
錯(cuò)誤:
useEffect(() => {
getData(id);
}, []);即使 id 變化也不會(huì)重新執(zhí)行。
應(yīng)寫(xiě):
useEffect(() => {
getData(id);
}, [id]);遵守 eslint:
react-hooks/exhaustive-deps
不要隨便關(guān):
// eslint-disable-next-line
很多閉包問(wèn)題來(lái)自錯(cuò)誤依賴(lài)。
解決方案4:useCallback + 依賴(lài)更新
錯(cuò)誤:
const getUser = useCallback(() => {
console.log(id);
}, []);id 永遠(yuǎn)舊。
正確:
const getUser = useCallback(() => {
console.log(id);
}, [id]);解決方案5:抽離自定義 Hook(推薦業(yè)務(wù)場(chǎng)景)
比如輪詢(xún):
錯(cuò)誤:
useEffect(() => {
const timer=setInterval(()=>{
fetchData(page);
},1000)
return ()=>clearInterval(timer)
},[])page 永遠(yuǎn)舊。
改:
function useLatest(value){
const ref=useRef(value);
useEffect(()=>{
ref.current=value;
},[value])
return ref;
}使用:
const pageRef = useLatest(page);
useEffect(() => {
const timer = setInterval(() => {
fetchData(pageRef.current);
},1000);
return ()=>clearInterval(timer);
},[])解決方案6:React 19 的useEffectEvent(未來(lái)推薦)
React 提供了解決閉包問(wèn)題的新方案:
const onVisit = useEffectEvent(() => {
console.log(count);
});
useEffect(()=>{
window.addEventListener('click',onVisit)
return ()=>{
window.removeEventListener('click',onVisit)
}
},[])事件始終讀取最新 state。
適合:
- 訂閱
- 監(jiān)聽(tīng)器
- effect 內(nèi)回調(diào)
前端開(kāi)發(fā)里可以記一個(gè)經(jīng)驗(yàn):
需要最新?tīng)顟B(tài):
→ 用 ref
依賴(lài)舊狀態(tài)更新:
→ 用函數(shù)式 setState
effect/callback 使用變量:
→ 補(bǔ)全依賴(lài)數(shù)組
長(zhǎng)生命周期回調(diào):
→ useLatest / useRef
React19:
→ useEffectEvent
像你做性能平臺(tái)、輪詢(xún)二維碼狀態(tài)、下載任務(wù)進(jìn)度這些場(chǎng)景,setInterval + useRef 基本是閉包問(wèn)題高發(fā)區(qū)。
到此這篇關(guān)于react解決 Hooks 閉包陷阱的問(wèn)題的文章就介紹到這了,更多相關(guān)react Hooks 閉包陷阱內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Reactjs?錯(cuò)誤邊界優(yōu)雅處理方法demo
這篇文章主要為大家介紹了Reactjs?錯(cuò)誤邊界優(yōu)雅處理方法demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
React?Native實(shí)際項(xiàng)目中的經(jīng)驗(yàn)和踩坑記錄
在當(dāng)今移動(dòng)應(yīng)用開(kāi)發(fā)的浪潮中,React Native以其跨平臺(tái)、高效開(kāi)發(fā)的特點(diǎn)脫穎而出,成為眾多開(kāi)發(fā)者首選的框架之一,這篇文章主要介紹了React Native實(shí)際項(xiàng)目中的經(jīng)驗(yàn)和踩坑記錄的相關(guān)資料,需要的朋友可以參考下2026-03-03

