第9回

useCallbackで関数をメモ化:React.memoと組み合わせた最適化

ReactのuseCallbackを解説します。関数のメモ化・React.memoとの組み合わせ・useEffectの依存配列での活用を実例で解説。useMemoとの違いと使い分けも明確にします。

·6分で読める

制作・運営:React Guide 運営者

関数を作らないHookではない

useCallback(fn, deps) は、依存が同じなら以前の関数を返します。引数として渡す関数式はレンダー時に評価されますが、Reactが返す関数の参照を再利用できます。関数をその場で実行するHookでもありません。

useMemoは計算した値、useCallbackは関数を再利用するために使います。どちらも最適化であり、すべてのイベントに付ける必要はありません。

メモ入力と追加ボタンを分ける

以下は src/App.tsx を置き換える例です。React Compilerを有効にしていない構成で、手動メモ化の働きを観察します。

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

const AddButton = memo(function AddButton({ onAdd }: { onAdd: () => void }) {
  console.log('追加ボタンをレンダー')
  return <button onClick={onAdd}>1つ追加</button>
})

export default function App() {
  const [count, setCount] = useState(0)
  const [note, setNote] = useState('')
  const handleAdd = useCallback(() => setCount(n => n + 1), [])

  return (
    <main>
      <label>メモ<input value={note} onChange={e => setNote(e.target.value)} /></label>
      <p>個数:{count}</p>
      <AddButton onAdd={handleAdd} />
    </main>
  )
}

handleAddはそのコンポーネントの安定したsetterだけを使うので、空配列にできます。ボタンのlabelなど別のpropsが変われば、関数だけを固定してもmemoは省略できません。

メモ入力時のログを観察した後、ローカルで const handleAdd = () => setCount(n => n + 1) に置き換えて比べます。初期のStrictModeによる追加ログを、入力によるログと混同しないでください。

古いpropsを固定しない

// コンポーネント内の抜粋。stepはprops。
const handleAdd = useCallback(() => {
  setCount(n => n + step)
}, [step])

stepを使うなら依存に含めます。[] にして警告を消してしまうと、後からstepを変更しても最初の値を使い続けます。

Effectのコールバックにも条件がある

親が毎回別のonSuccessを渡し、子のEffectがその関数で親のstateを更新すると、取得→親の更新→新しい関数→再取得という循環になりえます。関数が依存にあるだけでループするわけではありません。

単にstateへ結果を渡すだけなら、<Child onSuccess={setResult} /> とsetterを直接渡せます。加工が必要ならuseCallbackが候補です。取得とstate管理を同じ場所にまとめ、子の「取得完了を通知するEffect」をなくす方法もあります。

詳しい循環の切り分けは 依存配列の回へ。

検索結果が変われば行も変わる

検索結果を再取得して、各行のresultオブジェクトが新しくなった場合、onSelectだけを固定しても行のmemoは省略できません。すべてのpropsが比較対象です。「検索しても各行が再レンダーされない」という期待は誤りです。

一覧の選択操作は、liのクリックだけで実装せず、内側にbuttonを置くとキーボードでも操作できます。

<li><button onClick={() => onSelect(result.id)}>{result.title}</button></li>

判断の基準

  • memoされた重い子へ、変化しないハンドラーを渡す必要があるか。
  • その子の別のpropsが毎回変わっていないか。
  • 関数を依存にする設計を、Effect内への移動などで簡単にできないか。

まず正しいコードを作り、測定で効果を確認します。useCallbackがなくても正しく動くことが前提です。

React公式:useCallback、memoのprops比較

#react#hooks#useCallback#performance#intermediate