iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
Modern Web

現在就學React.js 系列 第 25

useCallback的使用時機 - Day25

  • 分享至 

  • xImage
  •  

在 React 中,當父組件重新渲染時,內部的變數和函式也會被重新創建,這可能導致子組件不必要的重新渲染,進而影響效能。為了避免這種情況,我們可以使用 memo 來包裹子組件,讓 React 記憶子組件的 props,當 props 改變時才重新渲染子組件。

但函式每次重新創建時,記憶 propsmemo 並不起作用。因此需要 useCallback 來記憶函式,避免每次父組件渲染時都被重新創建。

useCallback 會返回一個記住的函式,只有在其依賴項改變時才會重新創建這個函式,從而避免不必要的渲染。

基本語法:


const memoizedCallback = useCallback(() => {
  // 函式邏輯
}, [依賴項]);

useCallback 的適用場景:

  1. 函式作為 props 傳遞給子組件:當父組件狀態或 props 改變時,父組件重新渲染會導致函式重新創建,進而引發不必要的子組件渲染。useCallback 可以避免這種情況。
  2. 依賴外部變數的函式:如果某個函式依賴於外部的變數或狀態,且不希望每次父組件重新渲染時都重新創建該函式,可以使用 useCallback,並將這些依賴變數傳入依賴陣列。

範例:未使用 useCallback


import { useState, memo } from 'react'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])

  const increment = () => {
    setCount(c => c + 1)
  }

  // 沒有使用 useCallback 的 addTodo 函式
  const addTodo = () => {
    setTodos(t => [...t, 'New Todo'])
  }

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  )
}

// 使用 memo 優化 Todos 組件,避免不必要的重新渲染
const Todos = memo(({ todos, addTodo }) => {
  console.log('child render')
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => (
        <p key={index}>{todo}</p>
      ))}
      <button onClick={addTodo}>Add Todo</button>
    </>
  )
})

export default App

明明使用 memo優化組件後, 為什麼會重新渲染?

當你觀察上面的範例後,父組件重新渲染後,子組件也是有重新渲染的!

主要的原因是:

  • 每次 App 組件重新渲染,addTodo 函式都會重新創建一個新的記憶體參考。
  • Todos 組件的 props 會因為addTodo有變化而觸發 memo 的重新渲染,但實際上 todos 沒有發生變化。

使用 useCallback 解決問題

我們可以通過 useCallback 來記憶 addTodo 函式,這樣每次 App 重新渲染時,addTodo 的記憶體參考就不會變化,便能避免不必要的渲染。

修改後的程式碼:


import { useState, memo, useCallback } from 'react'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])

  const increment = () => {
    setCount(c => c + 1)
  }

  // 使用 useCallback 記憶 addTodo 函式
  const addTodo = useCallback(() => {
    setTodos(t => [...t, 'New Todo'])
  }, [todos])

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  )
}

const Todos = memo(({ todos, addTodo }) => {
  console.log('child render')
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => (
        <p key={index}>{todo}</p>
      ))}
      <button onClick={addTodo}>Add Todo</button>
    </>
  )
})

export default App

使用 useCallback

  1. useCallback 會記憶住 addTodo 函式,只有在 todos 改變時才會重新創建這個函式。
  2. 當父組件重新渲染時,只要 todos 沒有變化,addTodo 的記憶體參考就不會改變,Todos 組件也不會重新渲染。
  3. 使用 memouseCallback 組合後,可以有效避免子組件的無效渲染,提升效能。

總結

當子組件的props有函式傳遞時,若有想要用 memo 優化子組件時,也記得要搭配上useCallback ,能減少子組件不必要的重新渲染。

後記

本文將會同步更新到我的部落格

黃禎平 – Medium


上一篇
提升組件的效能-memo - Day24
下一篇
打造自己的 Hook:Custom Hook Day26
系列文
現在就學React.js 31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言