1. 程式人生 > 程式設計 >淺談react useEffect閉包的坑

淺談react useEffect閉包的坑

問題程式碼

看一段因為useEffect導致的閉包問題程式碼

const btn = useRef();
const [v,setV] = useState('');

useEffect(() => {
    lewww.cppcns.comt 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的依賴項陣列為空,所以在頁面渲染完成之後,內部程式碼只會執行一次,頁面銷燬再執行一次。此時在輸入框中輸入任意字元,再點選測試按鈕,得到的輸出為空,之後無論如何輸入任何字元,再點選測試按鈕時,輸出的結果仍為空。

為什麼會這樣呢?其實就是閉包所造成的。

產生原因

函式的作用域在函式定義的時候就決定了

給btn註冊點選事件時,作用域如下:

淺談react useEffect閉包的坑

能訪問到的自由變數v此時還是空值。當點選事件觸發時,執行點選回撥函式,此時先建立執行上下文,會拷貝作用域鏈到執行上下文中。

  • 如果未在輸入框內輸入字元,此時點選拿到的v還是原來那個v
  • 如果在輸入框內輸入了字元,此時呼叫了EBDnJGv
    setV修改了state,頁面觸發render,元件內部程式碼會重新執行一遍,重新聲明瞭一個v,v就不再是原來那個v,這裡點選事件裡作用域中的v還是舊的v,這是兩個不同的v

產生場景

  • 事件繫結。比如示例程式碼中,在頁面最初渲染完成後只繫結一次事件的情況,比如使用echarts,在useEffect中獲取echarts的例項並繫結事件
  • 定時器。頁面載入後註冊一個定時器,定時器內的函式也會產生如此的閉包問題。

解決辦法

針對這個閉包問題下面大致給出5種解決辦法

1. 以賦值方式直接修改v,並將修改v的方法用useCallback包裹起來

將修改v的方法用useCallback包裹起來,被useCallback包裹的函式將被快取,由於依賴項的陣列為空,所以這裡直接賦值的方式修改的v是舊的v,此種方法不推薦,因為setState才是官方推薦的修改state的方式,這裡仍http://www.cppcns.com

然使用setV只是為了觸發rerender

// v 的宣告 由 const 改為 var,方便直接修改
var [v,setV] = useState('');

const inputHandle = useCallback(e =>www.cppcns.com; {
    let { value } = e.target
    v = value
    setV(value)
},[])

2. 給useEffect的依賴項加上v

這也許是大多數人首先想到的辦法,既然v是舊的,那麼每次v更新的時候,重新註冊一次事件不就行了,但是這樣的會導致每次v更新都得重新註冊,理論應該只需要註冊一次的事件變成了多次。

3. 避免v被重新宣告

以let或var的方式宣告某個變數代替v,直接修改這個變數,而不是要setState相關函式觸發render,這樣就不會被重新宣告,點選的回撥函式裡就能拿到“最新”的值,但這個方法更不推薦,就此示例來說,input元件由於沒有rerender而至始至終都是顯示空值,不符合操作預期。

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',[]);

const inputHandle = e => {
    let { value } = e.target
    vRef.current = value
    setV(value)
}

return (
        <>
            <input value={v} onChange={inputHandle} />
            <button ref={btn} >測試</button>
        </>
    )

useRef的方案之所以有效,是因為每次input的change修改的是vRef這個物件的current屬性,而vRef始終是那個vRef,即使rerender,由於vRef是物件,所以變數儲存在棧記憶體中的值是該物件在堆記憶體中的地址,只是一個引用,只修改物件的某個屬性,該引用並不會改變。www.cppcns.com所以點選事件中的作用域鏈始終訪問的都是同一個vRef

淺談react useEffect閉包的坑

5. 將v換成物件型別

其實和使用useRef一樣,只要是物件,僅修改某個屬性也不會改變該state所指向的地址。

程式碼地址

點這裡看測試程式碼

到此這篇關於淺談react useEffect閉包的坑的文章就介紹到這了,更多相關react useEffect閉包內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!