useMemo は、依存値が変わらない間は計算結果を再利用する Hook です。大きな配列の絞り込みなど、本当に計測して高コストだと分かった計算を最適化します。
計算結果をキャッシュする
import { useMemo } from 'react';
function TodoList({ todos, filter }: { todos: Todo[]; filter: string }) {
const visibleTodos = useMemo(
() => todos.filter((todo) => todo.title.includes(filter)),
[todos, filter],
);
return <ul>{visibleTodos.map((todo) => <li key={todo.id}>{todo.title}</li>)}</ul>;
}
filter のような文字列や todos のような配列の参照が前回と同じなら、React は前回計算した配列を返します。ここで todos 自体を直接変更すると参照が同じまま内容だけ変わるため、キャッシュの前提が壊れます。state の配列を置き換えるという useState の原則は、memo 化でも重要です。
todos または filter が変わったときだけ関数が評価されます。依存配列には計算内で使う props、state、ローカル変数を正しく列挙します。
再レンダー自体を止める Hook ではない点に注意してください。親が再レンダーすれば TodoList は再実行されますが、依存値が同じなら visibleTodos の計算だけを省けます。また、同じ参照を返すことで memo 化した子へ渡す props を安定させる用途もあります。
正しさではなく最適化のために使う
useMemo がなくてもコードが正しく動くべきです。Effect の依存配列を安定させるためだけに付ける前に、オブジェクトを Effect 内で作るなど設計を簡単にできないか確認します。React Compiler を導入している場合は手動メモ化が不要になることもあります。
落とし穴
計算関数は純粋にします。開発時には問題を検出するため複数回呼ばれることがあります。また、メモ化自体にも比較と保持のコストがあります。入力が頻繁に変わる軽い計算を包んでも速くなりません。
useMemo のキャッシュは永続保存ではありません。コンポーネントが unmount されれば失われ、開発時や将来の React の最適化で破棄される可能性もあります。正しさに必要な値は state、ref、または外部ストアへ保存し、useMemo は性能のためだけに使います。
計測してから使う
数百件程度の配列処理より、複雑な子コンポーネントの描画や不要な Effect の方が問題になりやすいです。React DevTools Profiler で遅い操作を特定し、同じ入力で繰り返す重い計算かを確認します。メモ化はコードに依存関係を追加するため、速さと読みやすさの両方が改善する場合に採用します。
まとめ
useMemo は値を「記憶する state」ではなく、計算を省くキャッシュです。体感上の問題や計測結果がある箇所にだけ適用します。