概要
| フック | メモ化する対象 | 再計算のタイミング |
|---|---|---|
useCallback | 関数の参照 | 依存配列が変わったとき |
useMemo | 計算の結果 | 依存配列が変わったとき |
useCallback — 関数参照をメモ化
子コンポーネントに関数を props として渡すとき、親が再レンダリングされるたびに新しい関数参照が生成される。
useCallback を使うと依存値が変わらない限り同じ参照を返す。
import { useState, useCallback, memo } from "react";
// memo でラップされた子コンポーネント
const Button = memo(({ onClick, label }: { onClick: () => void; label: string }) => {
console.log(`${label} rendered`);
return <button onClick={onClick}>{label}</button>;
});
Button.displayName = "Button";
export function Counter() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);
// useCallback なし → other が変わるたびに新しい参照が生成される
const incrementWithout = () => setCount((c) => c + 1);
// useCallback あり → other が変わっても同じ参照を維持
const incrementWith = useCallback(() => setCount((c) => c + 1), []);
return (
<div>
<p>count: {count} / other: {other}</p>
<Button onClick={incrementWithout} label="without useCallback" />
<Button onClick={incrementWith} label="with useCallback" />
<button onClick={() => setOther((o) => o + 1)}>other++</button>
</div>
);
}
useMemo — 計算結果をメモ化
重い計算を毎レンダリング実行しないようにする。
import { useState, useMemo } from "react";
function heavyCalc(n: number): number {
// 重い処理の模擬
let result = 0;
for (let i = 0; i < n * 1000; i++) result += i;
return result;
}
export function HeavyList({ count }: { count: number }) {
const [filter, setFilter] = useState("");
// useMemo なし → filter 変更のたびに heavyCalc が実行される
// const result = heavyCalc(count);
// useMemo あり → count が変わったときのみ再計算
const result = useMemo(() => heavyCalc(count), [count]);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="フィルタ" />
<p>result: {result}</p>
</div>
);
}
ポイント
memo+useCallbackはセットで使うことが多いuseMemoは計算コストが高い処理に限定する- 依存配列の指定ミスに注意(ESLint の
exhaustive-depsルールを活用する) - 過剰なメモ化はコードを複雑にするだけなので、プロファイラで計測してから適用する