在這篇文章中,我將展示如何在 usestate hook react 應(yīng)用程序中創(chuàng)建閉包。
我不會解釋什么是閉包,因?yàn)殛P(guān)于這個(gè)主題的資源有很多,我不想重復(fù)。我建議閱讀@imranabdulmalik的這篇文章。
簡而言之,一個(gè)closure是(來自mozilla):
…捆綁在一起(封閉)的函數(shù)及其周圍狀態(tài)(詞法環(huán)境)的引用的組合。換句話說,閉包使您可以從內(nèi)部函數(shù)訪問外部函數(shù)的作用域。在 javascript 中,每次創(chuàng)建函數(shù)時(shí)都會創(chuàng)建閉包,在函數(shù)創(chuàng)建時(shí).
以防萬一你不熟悉這個(gè)詞詞匯環(huán)境,你可以閱讀@soumyadey的這篇文章或者這篇文章。
問題
在 react 應(yīng)用程序中,您可能會意外創(chuàng)建屬于使用 usestate 鉤子創(chuàng)建的組件狀態(tài)的變量的閉包。發(fā)生這種情況時(shí),您將面臨staleclosure問題,也就是說,當(dāng)您引用狀態(tài)的舊值時(shí),它同時(shí)發(fā)生了變化,因此它不再相關(guān)。
poc
我創(chuàng)建了一個(gè) demo react 應(yīng)用程序,其主要目標(biāo)是增加一個(gè)計(jì)數(shù)器(屬于狀態(tài)),該計(jì)數(shù)器可以在 settimeout 方法的回調(diào)中的閉包中關(guān)閉。
總之,這個(gè)應(yīng)用程序可以:
顯示計(jì)數(shù)器的值
計(jì)數(shù)器加1
啟動計(jì)時(shí)器,在五秒后將計(jì)數(shù)器加1。
計(jì)數(shù)器加10
下圖中,顯示了應(yīng)用程序的初始ui狀態(tài),計(jì)數(shù)器為零。
我們將分三步模擬柜臺關(guān)閉:
-
計(jì)數(shù)器加1
-
啟動計(jì)時(shí)器,五秒后加1
超時(shí)觸發(fā)前增加10
5秒后,計(jì)數(shù)器的值為2.
計(jì)數(shù)器的期望值應(yīng)該是12,但是我們得到2。
發(fā)生這種情況的原因是因?yàn)槲覀冊趥鬟f給settimeout的回調(diào)中創(chuàng)建了關(guān)閉計(jì)數(shù)器,并且當(dāng)觸發(fā)超時(shí)時(shí)我們設(shè)置計(jì)數(shù)器從其舊值(即 1)開始。
settimeout(() => {
setlogs((l) => [...l, `you closed counter with value: ${counter}\n and now i'll increment by one. check the state`])
settimeoutinprogress(false)
setstarttimeout(false)
setcounter(counter + 1)
setlogs((l) => [...l, `did you create a closure of counter?`])
}, timeoutinseconds * 1000);
登錄后復(fù)制
下面是app組件的完整代碼
function app() {
const [counter, setcounter] = usestate(0)
const timeoutinseconds: number = 5
const [starttimeout, setstarttimeout] = usestate(false)
const [timeoutinprogress, settimeoutinprogress] = usestate(false)
const [logs, setlogs] = usestate>([])
useeffect(() => {
if (starttimeout && !timeoutinprogress) {
settimeoutinprogress(true)
setlogs((l) => [...l, `timeout scheduled in ${timeoutinseconds} seconds`])
settimeout(() => {
setlogs((l) => [...l, `you closed counter with value: ${counter}\n and now i'll increment by one. check the state`])
settimeoutinprogress(false)
setstarttimeout(false)
setcounter(counter + 1)
setlogs((l) => [...l, `did you create a closure of counter?`])
}, timeoutinseconds * 1000);
}
}, [counter, starttimeout, timeoutinprogress])
function renderlogs(): react.reactnode {
const listitems = logs.map((log, index) =>
closure demo
counter value: {counter}
follow the istructions to create a closure of the state variable counter
-
set the counter to preferred value
start a timeout and wait for {timeoutinseconds} to increment the counter (current value is {counter})
increment by 10 the counter before the timeout
{ renderlogs() } ); } export default app;
登錄后復(fù)制
解決方案
解決方案基于useref鉤子的使用,它可以讓你引用渲染不需要的值。
所以我們在app組件中添加:
const currentcounter = useref(counter)
登錄后復(fù)制
然后我們修改settimeout的回調(diào),如下所示:
settimeout(() => {
setlogs((l) => [...l, `you closed counter with value: ${currentcounter.current}\n and now i'll increment by one. check the state`])
settimeoutinprogress(false)
setstarttimeout(false)
setcounter(currentcounter.current + 1)
setlogs((l) => [...l, `did you create a closure of counter?`])
}, timeoutinseconds * 1000);
登錄后復(fù)制
我們的回調(diào)需要讀取計(jì)數(shù)器值,因?yàn)槲覀冎坝涗浟水?dāng)前值以遞增它。
如果您不需要讀取值,只需使用功能符號更新計(jì)數(shù)器即可避免計(jì)數(shù)器關(guān)閉。
seCounter(c => c + 1)
登錄后復(fù)制
資源
dmitri pavlutin 使用 react hooks 時(shí)要注意過時(shí)的閉包
imran abdulmalik 掌握 javascript 中的閉包:綜合指南
keyur paralkar javascript 中的詞法范圍 – 初學(xué)者指南
souvik paul react 中的陳舊閉包
soumya dey 理解 javascript 中的詞法范圍和閉包
subash mahapatra stackoverflow






