淺談react useEffect閉包的坑
問題代碼
看一段因?yàn)閡seEffect導(dǎo)致的閉包問題代碼
const btn = useRef(); const [v, setV] = useState(''); useEffect(() => { let clickHandle = () => { console.log('v:', v); } btn.current.addEventListener('click', clickHandle) return () => { btn.removeEventListener('click', clickHandle) } }, []); const inputHandle = e => { setV(e.target.value) } return ( <> <input value={v} onChange={inputHandle} /> <button ref={btn} >測試</button> </> )
useEffect的依賴項(xiàng)數(shù)組為空,所以在頁面渲染完成之后,內(nèi)部代碼只會(huì)執(zhí)行一次,頁面銷毀再執(zhí)行一次。此時(shí)在輸入框中輸入任意字符,再點(diǎn)擊測試按鈕,得到的輸出為空,之后無論如何輸入任何字符,再點(diǎn)擊測試按鈕時(shí),輸出的結(jié)果仍為空。
為什么會(huì)這樣呢?其實(shí)就是閉包所造成的。
產(chǎn)生原因
函數(shù)的作用域在函數(shù)定義的時(shí)候就決定了
給btn注冊點(diǎn)擊事件時(shí),作用域如下:
能訪問到的自由變量v此時(shí)還是空值。當(dāng)點(diǎn)擊事件觸發(fā)時(shí),執(zhí)行點(diǎn)擊回調(diào)函數(shù),此時(shí)先創(chuàng)建執(zhí)行上下文,會(huì)拷貝作用域鏈到執(zhí)行上下文中。
- 如果未在輸入框內(nèi)輸入字符,此時(shí)點(diǎn)擊拿到的v還是原來那個(gè)v
- 如果在輸入框內(nèi)輸入了字符,此時(shí)調(diào)用了setV修改了state,頁面觸發(fā)render,組件內(nèi)部代碼會(huì)重新執(zhí)行一遍,重新聲明了一個(gè)v,v就不再是原來那個(gè)v,這里點(diǎn)擊事件里作用域中的v還是舊的v,這是兩個(gè)不同的v
產(chǎn)生場景
- 事件綁定。比如示例代碼中,在頁面最初渲染完成后只綁定一次事件的情況,比如使用echarts,在useEffect中獲取echarts的實(shí)例并綁定事件
- 定時(shí)器。頁面加載后注冊一個(gè)定時(shí)器,定時(shí)器內(nèi)的函數(shù)也會(huì)產(chǎn)生如此的閉包問題。
解決辦法
針對(duì)這個(gè)閉包問題下面大致給出5種解決辦法
1. 以賦值方式直接修改v,并將修改v的方法用useCallback包裹起來
將修改v的方法用useCallback包裹起來,被useCallback包裹的函數(shù)將被緩存,由于依賴項(xiàng)的數(shù)組為空,所以這里直接賦值的方式修改的v是舊的v,此種方法不推薦,因?yàn)閟etState才是官方推薦的修改state的方式,這里仍然使用setV只是為了觸發(fā)rerender
// v 的聲明 由 const 改為 var,方便直接修改 var [v, setV] = useState(''); const inputHandle = useCallback(e => { let { value } = e.target v = value setV(value) }, [])
2. 給useEffect的依賴項(xiàng)加上v
這也許是大多數(shù)人首先想到的辦法,既然v是舊的,那么每次v更新的時(shí)候,重新注冊一次事件不就行了,但是這樣的會(huì)導(dǎo)致每次v更新都得重新注冊,理論應(yīng)該只需要注冊一次的事件變成了多次。
3. 避免v被重新聲明
以let或var的方式聲明某個(gè)變量代替v,直接修改這個(gè)變量,而不是要setState相關(guān)函數(shù)觸發(fā)render,這樣就不會(huì)被重新聲明,點(diǎn)擊的回調(diào)函數(shù)里就能拿到“最新”的值,但這個(gè)方法更不推薦,就此示例來說,input組件由于沒有rerender而至始至終都是顯示空值,不符合操作預(yù)期。
4. 使用useRef代替useState
const btn = useRef(); const vRef = useRef(''); const [v, setV] = useStat(''); useEffect(() => { let clickHandle = () => { console.log('v:', vRef.current); } btn.current.addEventListener('click', clickHandle) return () => { btn.removeEventListener('click', clickHandle) } }, []); const inputHandle = e => { let { value } = e.target vRef.current = value setV(value) } return ( <> <input value={v} onChange={inputHandle} /> <button ref={btn} >測試</button> </> )
useRef的方案之所以有效,是因?yàn)槊看蝘nput的change修改的是vRef這個(gè)對(duì)象的current屬性,而vRef始終是那個(gè)vRef,即使rerender,由于vRef是對(duì)象,所以變量存儲(chǔ)在棧內(nèi)存中的值是該對(duì)象在堆內(nèi)存中的地址,只是一個(gè)引用,只修改對(duì)象的某個(gè)屬性,該引用并不會(huì)改變。所以點(diǎn)擊事件中的作用域鏈?zhǔn)冀K訪問的都是同一個(gè)vRef
5. 將v換成對(duì)象類型
其實(shí)和使用useRef一樣,只要是對(duì)象,僅修改某個(gè)屬性也不會(huì)改變該state所指向的地址。
代碼地址
點(diǎn)這里看測試代碼
到此這篇關(guān)于淺談react useEffect閉包的坑的文章就介紹到這了,更多相關(guān)react useEffect閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VSCode配置react開發(fā)環(huán)境的步驟
本篇文章主要介紹了VSCode配置react開發(fā)環(huán)境的步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12使用react context 實(shí)現(xiàn)vue插槽slot功能
這篇文章主要介紹了使用react context 實(shí)現(xiàn)vue插槽slot功能,文中給大家介紹了vue的slot的實(shí)現(xiàn)方法,需要的朋友可以參考下2019-07-07解決React報(bào)錯(cuò)Property value does not exist&n
這篇文章主要為大家介紹了React報(bào)錯(cuò)Property value does not exist on type HTMLElement解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12詳解如何在React單頁面應(yīng)用中捕獲錯(cuò)誤
在當(dāng)前的Web開發(fā)中,使用React構(gòu)建單頁面應(yīng)用(SPA)已經(jīng)成為一種常見的做法,然而,當(dāng)應(yīng)用程序遇到錯(cuò)誤時(shí),有可能會(huì)導(dǎo)致整個(gè)頁面崩潰,給用戶帶來不好的體驗(yàn),本文將介紹如何在React單頁面應(yīng)用中捕獲錯(cuò)誤,以防止整個(gè)頁面的崩潰,需要的朋友可以參考下2023-09-09react搭建環(huán)境時(shí)執(zhí)行npm start報(bào)錯(cuò)start: 'react-scripts&
這篇文章主要介紹了react搭建環(huán)境時(shí)執(zhí)行npm start報(bào)錯(cuò)start: 'react-scripts start'的解決方案,具有很好的參考價(jià)值,希望杜對(duì)大家有所幫助,2023-10-10react使用css module無法重寫bootstrap樣式問題及解決
這篇文章主要介紹了react使用css module無法重寫bootstrap樣式問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11react用Redux中央倉庫實(shí)現(xiàn)一個(gè)todolist
這篇文章主要為大家詳細(xì)介紹了react用Redux中央倉庫實(shí)現(xiàn)一個(gè)todolist,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09