[React] Debouncing with useCallback Hook

·
react
lodash

本文介紹 React 當中 useCallback 這個 Hook 的使用概念,並且結合 Lodash 實作 Debouncing Search 的功能幫助理解 useCallback 的作用。

什麼是 useCallback Hook

useCallback 允許我們儲存跨元件執行的「函式」,它會把函式儲存在 React 內存記憶體,讓使用時的物件位址都是指向同一個地方,以避免無謂的重新渲染。

使用方式就是用 useCallback 包住我們要儲存的函式,它會回傳一個值(也就是你包裝的那個函式)。當我們包裝完成後,這個函式的 Functional Component 再進行重新評估執行時,函式就不會再被重新建立了。

// 回傳一個 memoized 的 callback
const memoizedCallback = useCallback(() => {
  doSomething(a, b)
}, [a, b])

這時候使用像是 React.memo 時,就能夠知道傳入的函式是相同的物件。儘管 Props 傳入的是 Object,仍可以在父元件重新渲染時,不會重新分配記憶體位址,減少不必要的重新渲染

Dependency Array

useCallback 裡面也要放 Dependencies,要放的內容其實就跟 useEffect 一樣,就是把我們在函式中使用到的、關於元件的任何東西,像是 Props 或者 State 都放在裡面。另外,就如同 useEffect 的規則,Browser 內建功能如 LocalStorage 或是 React 的 setState 等等是不需要被放入的。

// 這裡用到 setState 但是不用放入 dependency,因為 React 保證 setState 不會變化
const toggleParagraphHandler = useCallback(() => {
  setShowParagraph(prevShowParagraph => !prevShowParagraph)
}, [])

如果函式有使用到一些變數,因為有 Closure 關住原本的狀態,所以就算外部更改了變數,此時 useCallback 包住的函式的變數值,仍然會是傳入時的值。

如果希望根據變數改變,而重新生成函式的話,我們就得把那個變數加入 Dependency。例如:我們希望 allowToggle 更改時,就重新生成 toggleParagraphHandler Function。

const toggleParagraphHandler = useCallback(() => {
  if (allowToggle) {
    setShowParagraph(prevShowParagraph => !prevShowParagraph)
  }
}, [allowToggle])

範例練習:Lodash Debounce with React Hooks

useCallback 常見的其中一個使用情境,就是在實作 Throttle 或 Debounce 功能的時候。

搜尋時,我們透過 Lodash 的 _.debounce() 可以得到一個回傳的 Debounced 函式,並且函式使用 window.setTimeout 處理的 Timer 每次都不一樣,所以可以達到等待輸入的效果。換句話說,每一次回傳的 Debounced 函式都會是全新的,因此我們必須想辦法記憶這個函式,避免 React 元件重新渲染。

方法一:透過 useRef 避免重新渲染

我們可以透過 useRef 來記錄資料,因為透過 useRef 記錄的資料即使改變也不會造成重新渲染(若使用 useState 則會造成 Re-render)。

做法如下,使用 useRef 儲存資料,並使用 .current 屬性取得最新的 Debounced Function。

const debouncedSearch = useRef(
  debounce(async params => {
    await doSearch(params)
  }, 1000),
  []
).current

方法二:透過 useCallback 記憶函式

除了上述 useRef 的實作方式之外,使用 useCallback 也可以完成一樣的效果,同時能夠讓程式碼更加語意化、更好理解,個人比較推薦這個做法。

const debouncedSearch = useCallback(
  debounce(async params => {
    await doSearch(params)
  }, 1000),
  []
)

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

  • React useCallback Hook
  • Lodash Debounce with React Hooks

References