第9回
useCallbackで関数をメモ化:React.memoと組み合わせた最適化
ReactのuseCallbackを解説します。関数のメモ化・React.memoとの組み合わせ・useEffectの依存配列での活用を実例で解説。useMemoとの違いと使い分けも明確にします。
制作・運営: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がなくても正しく動くことが前提です。
同じテーマの教材
useTransitionとuseDeferredValue:入力と一覧の更新を分ける
検索入力と一覧の更新を分けるサンプルでuseTransitionとuseDeferredValueを解説します。更新の優先度、memoとの関係、別スレッドや通信の間引きとの違いを確認します。
useMemoでパフォーマンス最適化:重い計算をメモ化する
ReactのuseMemoを解説します。メモ化の仕組み・高コスト計算の最適化・参照の安定化、そしてuseMemoを使うべき場面・使わなくてよい場面の判断基準を実例付きで学べます。
React Compiler 1.0:Viteへの導入とメモ化の確認方法
安定版になったReact Compilerを実際に試す。Vite 8環境への導入からDevToolsでの効果確認、既存のuseMemoとの付き合い方までを動かしながら解説する。