React API memo や React Hooks の useMemo・useCallback は、どんな機能や違いがあるのかな?
今回は、React API である memo と React Hooks の useMemo・useCallback について、それぞれの違いや使い方、代表的なユースケースをわかりやすく解説します。
memo / useMemo / useCallback が生まれた背景
React.js は便利ですが、コンポーネントが更新されるたびにJSX やオブジェクト、配列、関数が毎回新しく生成 されます。
通常は問題になりませんが、次のような場合には 不要な処理が発生する 可能性があります。
- 重い計算を毎回実行している
- 子コンポーネントが大量にある
- props が変わっていないのに再レンダリングされる
こうした問題を解決するために登場したのが、前回と同じなら再利用(メモ化)する という考え方で実現される memo / useMemo / useCallback です。
memo / useMemo / useCallback とは?
memo / useMemo / useCallback はすべて 再レンダリング最適化 を目的としていますが、何を再利用(メモ化)するか という点で役割が異なります。
※ useMemo や useCallback 単体では、コンポーネントの再レンダリングは抑制されません。主に memo と組み合わせて使うことで、再レンダリング最適化 の効果を発揮します。
| API・React Hook | メモ化する対象 | 処理 | 効果 |
|---|---|---|---|
| memo | コンポーネントの描画結果 | props が前回と同じ場合に、コンポーネントの再レンダリングをスキップする | 子コンポーネントの再レンダリングを抑える |
| useMemo | 値(計算結果) | 依存配列が変わらない限り、値(計算結果)の再計算をスキップする | props に渡す値の参照を固定する |
| useCallback | 関数 | 依存配列が変わらない限り、関数の再生成をスキップする | props に渡す関数の参照を固定する |
使用する際の注意点
useCallback で 関数をメモ化(再利用)していても、それを受け取る子コンポーネントが memo でラップされていなければ、再レンダリングは発生 します。
逆に、memo を使っていても、props に毎回新しい関数やオブジェクトを渡している場合、
props が変更されたと判定され、再レンダリングが発生 します。
コード例
memo
- memo でコンポーネントの再描画を抑制しているため、MemoTitle のログが初回のみ表示
※ Title のログは毎回表示される
🔹 useCallback
- useCallback を設定していても、子コンポーネント側で memo していない場合、毎回レンダリングされてしまう
- useCallback で 関数の再生成を抑制 しているため、 初回のみ MemoCountButton: CountDown のログが表示され、MemoCounter: Down は初回と更新時のみログが表示される
※ CountButton: CountUp, Counter: Up のログは毎回表示される
🔹 useMemo
- useMemo で 値の再計算を抑制 しているため、 UseMemoCounter: のログが初回と更新時のみ表示される
※ NoUseMemoCounter: のログは毎回表示される
import { memo, useState, useMemo, useCallback } from 'react';
// Title のログが毎回表示
const Title = () => {
console.log('Title');
return <h2>Title</h2>;
};
// memo:コンポーネントの再描画を抑制する
// MemoTitle のログが初回のみ表示
const MemoTitle = memo(() => {
console.log('MemoTitle');
return <h2>Memo Title</h2>;
});
// CountButton: CountUp のログが毎回表示
const CountButton = ({ id, onIncrement, children }) => {
console.log('CountButton:', id);
return <button onClick={onIncrement}>{children}</button>;
};
// Counter: up のログが毎回表示
const Counter = ({ id, value }) => {
console.log('Counter:', id);
return (
<p>
{id} 現在の値:
{value}
</p>
);
};
// memo:コンポーネントの再描画を抑制する(useCallback)
// MemoCountButton: Countdown のログが初回のみ表示
const MemoCountButton = memo(({ id, onDecrement, children }) => {
console.log('MemoCountButton:', id);
return <button onClick={onDecrement}>{children}</button>;
});
// memo:コンポーネントの再描画を抑制する(useCallback)
// MemoCounter: down のログが初回と更新時のみ表示
const MemoCounter = memo(({ id, value }) => {
console.log('MemoCounter:', id);
return (
<p>
{id} 現在の値:
{value}
</p>
);
});
export default function MemoUseMemoUseCallbackPage() {
const [count, setCount] = useState(0);
const [countUp, setCountUp] = useState(0);
const [countDown, setCountDown] = useState(0);
// increment のログが毎回表示
const increment = () => {
console.log('increment');
setCountUp((cnt) => cnt + 1);
};
// useCallback:関数の再生成を抑制する
// decrement のログが初回と更新時のみ表示
const decrement = useCallback(() => {
console.log('decrement');
setCountDown((cnt) => cnt - 1);
}, []);
// NoUseMemoCounter: のログが毎回表示
const NoUseMemoCounter = () => {
console.log('NoUseMemoCounter:');
return count + 100;
};
// useMemo:値の再計算を抑制する
// UseMemoCounter: のログが初回と更新時のみ表示
const UseMemoCounter = useMemo(() => {
console.log('UseMemoCounter:');
return count + 300;
}, [count]);
return (
<div>
<Title />
<Counter id="Up" value={countUp} />
<CountButton id="CountUp" onIncrement={increment}>
カウントアップ
</CountButton>
<br />
<p>No UseMemo 値: {NoUseMemoCounter()}</p>
<p>UseMemo 値: {UseMemoCounter}</p>
<button onClick={() => setCount((cnt) => cnt + 1)}>
UseMemo カウントアップ
</button>
<br />
<MemoTitle />
<MemoCounter id="Down" value={countDown} />
<MemoCountButton id="CountDown" onDecrement={decrement}>
カウントダウン
</MemoCountButton>
</div>
);
}
処理フロー
- 更新(リロード) ※全てログに表示され、レンダリングを確認
- カウントアップ ボタンを押す
ログに「increment, NoUseMemoCounter, Title, Counter: Up, CountButton: CountUp」が表示され、レンダリングを確認 - 2回目の更新(リロード) ※全てログに表示され、レンダリングを確認
- カウントダウン ボタンを押す
ログに「decrement, NoUseMemoCounter, Title, Counter: Up, CountButton: CountUp, MemoCounter: Down」が表示され、レンダリングを確認 - 3回目の更新(リロード) ※全てログに表示され、レンダリングを確認
- UseMemo カウントアップ ボタンを押す
ログに「UseMemoCounter, NoUseMemoCounter, Title, Counter: Up, CountButton: CountUp」が表示され、レンダリングを確認

まとめ
React API である memo と React Hooks の useMemo・useCallback について、いかがでしたでしょうか?
memo / useMemo / useCallback はすべて 再レンダリング最適化 を目的としていますが、何を再利用するか(メモ化するか) という点で役割が異なります。
また useMemo や useCallback は、 memo と組み合わせて使うことで 再レンダリング最適化 の効果を発揮します。
まずはそれぞれの使い方をしっかり押さえ、小さな コンポーネント から試しながら、少しずつ memo / useMemo / useCallback に慣れていきましょう。


コメント