接下來這一篇算是簡單的一篇,因為我們只會快速認識一下 useCallback
,而 useCallback
是什麼呢?就讓我們繼續往下看吧。
useCallback
其實與前面 useMemo
非常相似,甚至可以說是 useMemo
的變體也不為過,但...這樣講很模糊,到底 useMemo
跟 useCallback
有何差異呢?
簡單來講這兩個 Hook 都是為了優化 React 渲染效能而誕生的,只是這兩者各自重點皆不同
useMemo
useCallback
記憶是什麼呢?講記憶的感覺好像非常可怕,因此所以你也可以把它理解成「緩存」、「暫存」的概念,所以將上面稍微修正一下就變成以下
useMemo
useCallback
那麼 useMemo
跟 useCallback
有何差異呢?接著就讓我們來看一下範例。
請注意,以下寫法僅僅只是示範,實務上並不建議你這樣做
而 useMemo
與 useCallback
都是接受一個函式與陣列,並且都是一個表達式
const App = () => {
const exampleMemo = React.useMemo(() => 1 + 1, []);
const exampleCallback = React.useCallback(() => 1 + 1, []);
React.useEffect(() => {
console.log('useMemo', exampleMemo);
console.log('useCallback', exampleCallback);
})
return (
<div>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
但是以結果來講,兩者輸出的都是不同的東西,分別是 2
跟 () => 1 + 1
,就如同前面所說的 useMemo
是回傳記憶的值,而 useCallback
是回傳記憶的函式。
那麼這時候這時候問題來了,useCallback
比較常見於哪裡呢?這讓我們先來看一個簡單範例
const App = () => {
const [count, setCount] = React.useState(0);
const getData = () => {
setTimeout(() => {
setCount((pre) => pre + 1)
}, 2000);
}
React.useEffect(() => {
getData()
}, []);
return (
<div>
{ count }
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
在上方的範例程式碼中,我們使用 setTimeout
模擬 AJAX 的行為,約兩秒後會針對 count
進行加 1
的動作,然而畫面也會有所更新,這邊基本上都是沒有什麼問題的,並且只會執行一次。
但這時候若我們稍微調整一下,拆出一個元件變成以下
const Child = ({ count, getData }) => {
React.useEffect(() => {
getData()
}, [ getData ]);
return (
<div>
{count}
</div>
)
}
const App = () => {
const [count, setCount] = React.useState(0);
const getData = () => {
setTimeout(() => {
setCount((pre) => pre + 1)
}, 2000);
}
return (
<div>
<Child count={count} getData={getData} />
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
這時候將範例改成以上之後,有趣的事情發生了,setTimeout
會不停的重複執行,這是為什麼呢?
那麼我們簡單的分析一下上面程式碼的運作流程
App
元件被渲染,getData
與 count
也被傳遞給 Child
元件Child
元件被渲染,並且使用 useEffect
進行監聽,當 getData
被改變時,就會執行 getData
函式getData
函式被執行,並且進行 setTimeout
的動作,約兩秒後會執行 setCount
進行 count
的更新到目前為止上方都還沒有什麼問題,但核心重點在於「約兩秒後會執行 setCount
進行 count
的更新」這一段
count
被更新,因此觸發 re-render App
元件App
元件因為被重新渲染,因此 getData
與 count
也跟著被重新生成並且傳遞給 Child
元件Child
的 useEffect
發現 getData
被重新生成,因此會再次執行 getData
函式就這樣子發生了無限循環事件。
那麼我們該如何解決呢?這時候就是 useCallback
表演的時候到了,將程式碼調整成以下
const Child = ({ count, getData }) => {
React.useEffect(() => {
getData()
}, [ getData ]);
return (
<div>
{count}
</div>
)
}
const App = () => {
const [count, setCount] = React.useState(0);
const getData = React.useCallback(() => {
setTimeout(() => {
setCount((pre) => pre + 1)
}, 2000);
}, [])
return (
<div>
<Child count={count} getData={getData} />
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
此時你就可以發現不會一直觸發 setTimeout
了,因為 getData
被包裝成 useCallback
之後,getData
的記憶體位置就不會被重新生成,因此 useEffect
也就不會再次執行 getData
函式。
那麼上方就是一個簡單的 useCallback
說明與範例哩~
本文將會同步更新到我的部落格