useMemoとは
useMemoとは、「計算結果を記憶(メモ化)して、不要な再計算を防ぐ」ためのフックです。
Reactのリレンダー(再描画)時、複雑な計算を毎回実行するとパフォーマンスに影響します。useMemo を使うとことで、依存値(dependencies)が変わらなければ計算処理は再実行されず前の結果を再利用(キャッシュ)されます。依存値が変わった場合にのみコールバック関数が再実行され、新しい結果を返します。
useMemoを使わない場合:
以下のように毎回レンタリング時に重い処理が実行されてしまいます。
import React, { useState } from 'react';
function heavyCalc(num: number): number {
console.log('💥 重い計算中...');
for (let i = 0; i < 1_000_000_000; i++) {} // めっちゃ重いループ
return num * 2;
}
export default function WithoutUseMemo() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const result = heavyCalc(count); // ❶ 毎回レンダリングごとに実行される!
return (
<div>
<h2>計算結果: {result}</h2>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)} // ❷ テキスト入力するたびにレンダリング → ❶発動
/>
</div>
);
}
useMemoを使った場合:
useMemoを使うことで、以下のように依存配列([count])が変更されたときだけ関数(heavyCalc)が実行されるようになります。
import React, { useState, useMemo } from 'react';
function heavyCalc(num: number): number {
console.log('💥 重い計算中...');
for (let i = 0; i < 1_000_000_000; i++) {}
return num * 2;
}
export default function WithUseMemo() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const result = useMemo(() => heavyCalc(count), [count]); // ✅ countが変わるときだけ計算される!
return (
<div>
<h2>計算結果: {result}</h2>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)} // これでは heavyCalc は走らない!
/>
</div>
);
}
基本構文
const memoizedValue = useMemo(
() => expensiveComputation(a, b),
[a, b]
);
- 第1引数:重い計算をまとめた関数
- 第2引数:依存配列
- 依存配列の要素(aやb)が変わらなければ、次回以降はキャッシュされた値を返します
具体的な使い方
偶数フィルタのキャッシュ
import React, { useState, useMemo } from 'react';
function NumberList() {
const [showEven, setShowEven] = useState(true);
const numbers = Array.from({ length: 1000000 }, (_, i) => i + 1);
const filtered = useMemo(() => {
console.log('Filtering...');
return numbers.filter(n => showEven ? n % 2 === 0 : n % 2 !== 0);
}, [showEven]);
return (
<div>
<button onClick={() => setShowEven(v => !v)}>
{showEven ? '奇数のみ表示' : '偶数のみ表示'}
</button>
<div>表示中の数: {filtered.length}</div>
</div>
);
}
- showEven が切り替わるときのみフィルタ処理(重い)を実行
- 簡単に切り替えただけでは再フィルタされず性能が安定する
ソートされたリスト
function PostList({ posts }) {
const sortedPosts = useMemo(() => {
return [...posts].sort((a, b) => b.date - a.date);
}, [posts]);
return (
<ul>
{sortedPosts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
- ソート処理を毎回しないようにし、posts が変わらない限り同じ結果を再利用します。
- React.memo や他の比較チェックと組み合わせれば、不要な再描画を抑制可能です
useEffectとの違い
useMemo
- レンダーの直中に呼ばれます。
- React がコンポーネントを描画するとき、JSX を評価する前にメモ化のチェック&計算を行う。
- 副作用的な処理は絶対に行わない(React ルール違反)。
useEffect
- レンダー後(描画が終わった後)に呼ばれます。
- DOM がブラウザに反映されたタイミングで走るので、副作用処理に安全。
- ブラウザ API(sessionStorage, localStorage, fetch など)を使うのに最適。