iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

昨天我們介紹了 useMemo,這個 hook 可以幫助我們記憶依賴參數,當參數變化時才會重新執行函式,並且取得函式回傳的值。
今天要來介紹的是 useCallback,跟 useMemo 相似,都是記憶依賴參數,變化時再重新執行,但回傳值及使用方式上又所不同。

useCallback 語法

const someFn = (a, b)=>{...}
const getValue = useCallback(()=>someFn(a, b), [a, b])

跟 useMemo 一樣,都是在第一個參數中放入一個 callback function,並且在第二個參數中放入依賴參數:一個含要偵測變數的 Array,當 Array 中的值發生變化時,就會執行 callback function。

useCallback 特性

useCallback 與 useMemo 相同,都是為了避免 React 重複不必要的渲染或執行、提升 React 的效能為目的設計的。但 useCallback 是回傳 function, useMemo 會回傳 callback function 執行後的值,而 useCallback 則是會回傳整個傳入的 function。
useCallback 的主要目的在於避免 function 因為元件的重新渲染而不斷的重新建立,造成不必要的重新渲染。

應用 1:搭配 React.memo

前天有介紹過 React.memo,這是一個會比較元件前後的 props 有無不同,若有變化再行渲染的一個 HOC,但若是 props 是物件,那 React.memo 就沒轍了。
為了解決這個問題,可以搭配使用 useCallback。
例如有一個傳入 function 當 props 的元件:

const Child = memo(({fn})=>{
    const counter = useRef(0);
    useEffect(()=>{
        counter.current++;
    });
  
    return (
        <div>
            <p>answer: {fn()}</p>
            <p>Child 更新 {counter.current} 次</p>
        </div>
    )
})

const Parent = () =>{
    const [count, setCount] = useState(0);
  const a = 1;
  const b = 2;
  const myFunction = ()=>{
    return a+b
  }
  
    return (
        <div>
            <Child fn={myFunction}/>
            <button onClick={()=> setCount((prevCount)=>prevCount+1)}>Parent 更新 {count} 次</button>
        </div>
    )
}

可以看到,一個 function 被傳入,當父元件變化時,傳入的 function 完全沒有變化,即使子元件使用了 React.memo,子元件仍然會跟著更新。
要解決這個問題,可以使用 useCallback:

  const myFunction = useCallback(()=>{
    return a+b
  }, []);

這樣子元件就不會重新渲染了。

useCallback 中使用 state

如果在 function 中的不是單純的變數,而是 state 的話,要怎麼做呢?
一般來說可能會想到這樣:

const [num, setNum] = useState(0);
const myFunction = useCallback(() => {
    setNum(num + 1)
}, []);

//...
<Child myFunction={myFunction} num={num}/>

但是這樣做了之後會發現:使用函式一次後, num 值就不再更新了!
會這樣的原因在於 myFunction 取 state 的方式是透過閉包取得同在一個詞法作用域 (lexical scope) 的值,但是在 React 中 state 值是 immutable 的,可以理解為 myFunction 取得 state 後,由於沒有再更新,因此函式內的 state 保持著初始值,自然 state 不會再更新。
要處理這個函式,我們可以用 useState 用 callback function 更新 state 的方法:

const myFunction = useCallback(()=>{
    setNum(prevNum => prevNum + 1);
}, [])

useState 會使用前一個 state 的值,而不是使用閉包取得外面的 state ,因此可以正常使用。

應用 2:function 使用 props/ state,且被多個 useEffect 使用

由於 useCallback 在渲染時執行,因此可以用作在執行 useEffect 之前的確保,確保 function 中已經先使用了 props/ state:

const Child = ({path}) => {
    const getUrl = useCallback((query)=>{
        return `https://example.com/${path}?query=${query}`
    }, [path])
    
    useEffect(()=>{
        const fetchUrl = async()=>{
            const data = await fetch(getUrl("js"));
            ...
        }
    }, [getUrl])
    
    useEffect(()=>{
        const fetchUrl = async()=>{
            const data = await fetch(getUrl("python"));
            ...
        }
    }, [getUrl])
    
    return (
        <div>
            ...
        </div>
    )
}

useCallback、useMemo 提供了方法,幫助我們能更好的提升程式效能,讓我們能夠確保我們的網頁能運行的更加快速及順暢。

參考資料
如何錯誤地使用 React hooks useCallback 來保存相同的 function instance
什麼時候該使用 useMemo 跟 useCallback
Day9-React Hook 篇-認識 useCallback
useCallback - Hooks API 參考


上一篇
[Day26]用 React 讓網站動起來:useMemo
下一篇
[Day28]用 React 讓網站動起來:自定義 hook
系列文
用React讓網頁動起來: React基礎與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言