[React] useMemoはいつ使うべきなのか
はじめに
React で状態管理をする際に useState を使うことが多いですが、パフォーマンス最適化のために useMemo を使うべき場面もあります。しかし、setCount のような単純な状態変更を行う場合、useMemo とuseState の違いが分からないことが多いです。
例えば、以下のようなカウンターを作成するとします。
import { useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増やす</button>
</div>
);
};
この場合、useMemo を使う意味はほとんどありません。では、どのような場面で useMemo を使うべきなのでしょうか?
useMemo が有効な場面
useMemo の主な用途は「計算コストの高い処理を最適化すること」です。たとえば、以下のようなリストフィルタリングの処理があるとします。
❌ useState のみを使用した場合(最適化なし)
import { useState } from "react";
const heavyCalculation = (items) => {
console.log("重い計算を実行中...");
return items.filter(item => item.includes("a"));
};
const Example = () => {
const [query, setQuery] = useState("");
const [items] = useState(["apple", "banana", "cherry", "grape", "orange"]);
const filteredItems = heavyCalculation(items);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
このコードでは query を変更するたびに heavyCalculation が実行されますが、query の変更は items とは関係がないため、毎回不要な再計算が発生しています。
✅ useMemo を使用して最適化した場合
import { useState, useMemo } from "react";
const heavyCalculation = (items) => {
console.log("重い計算を実行中...");
return items.filter(item => item.includes("a"));
};
const Example = () => {
const [query, setQuery] = useState("");
const [items] = useState(["apple", "banana", "cherry", "grape", "orange"]);
const filteredItems = useMemo(() => heavyCalculation(items), [items]);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
このように useMemo を使うことで query を変更しても filteredItems の計算が不要になり、無駄な計算を防ぐことができます。
useState と useMemo の適切な使い分け
useState |
useMemo |
|
|---|---|---|
| 目的 | UI の状態管理 | 計算コストの高い処理をキャッシュ |
| いつ使う? | ユーザーの操作で変化するデータ | 計算結果の再利用が必要な場合 |
| トリガー |
setState を呼ぶたびに再レンダリング |
依存配列 ([]) の値が変わると再計算 |
| 副作用 | あり (setState による UI 更新) |
なし(計算結果のキャッシュのみ) |
| 再レンダリング |
setState を呼ぶたびに発生 |
依存値が変わらない限り発生しない |
| 使いどころ | ボタンのクリック、フォーム入力 | 配列の map, filter の最適化、オブジェクトの安定化 |
useMemo を使うべき場面
-
計算コストが高い処理がある場合
- 配列の
map,filter,reduceなどの処理が重い - オブジェクトの生成や
JSON.parse(JSON.stringify(obj))のような処理が頻繁に行われる
- 配列の
-
無関係な state の変更で不要な再計算が発生する場合
-
queryのように、他の state の変更とは関係ない計算が何度も実行される
-
-
レンダリング時に毎回オブジェクトや関数が新しく生成されるケース
-
useEffectの依存関係としてオブジェクトを渡す場合
-
3が少しわかりにくいので具体例を踏まえて説明します。
例えば、以下のコードでは、data というオブジェクトを useEffect の依存配列に渡しています。
import { useState, useEffect, useMemo } from "react";
const Example = () => {
const [count, setCount] = useState(0);
const data = { value: 42 };
useEffect(() => {
console.log("useEffect が実行されました");
}, [data]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>増やす</button>
</div>
);
};
dataは{ value: 42 }という同じオブジェクトを保持しているように見えますが、コンポーネントが再レンダリングされるたびに新しい{ value: 42 }が作られます。
それによって、毎回useEffectが実行されることになってしまいます。
解決策: useMemoを使う
この問題を解決するには、useMemoを使ってdataをキャッシュします。
import { useState, useEffect, useMemo } from "react";
const Example = () => {
const [count, setCount] = useState(0);
const data = useMemo(() => ({ value: 42 }), []); // 🎯 依存配列が空なので、最初のレンダリング時にのみ作成される
useEffect(() => {
console.log("useEffect が実行されました");
}, [data]); // `data` が変わらない限り useEffect は再実行されない
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>増やす</button>
</div>
);
};
これでdataのメモリアドレスが変わらなくなり、useEffectが毎回実行される問題を防げます🙌
useMemo を使わなくてもよい場面
-
単純な状態管理のためだけに使う場合
-
useStateのみで十分なケース(例:countの更新)
-
-
レンダリングコストが低い処理に対して使う場合
- 固定値や小さい配列のレンダリング(map などの処理が少ない)
- 単純な数値計算(四則演算や小規模な reduce)
- 小規模なオブジェクトの生成(単純なキー・バリューのオブジェクト)
-
useCallbackで管理すべき関数のキャッシュと混同する場合- 関数のキャッシュには
useCallbackを使うべき
- 関数のキャッシュには
まとめ
useState は UI の状態管理 に適しており、useMemo は 計算コストの高い処理を最適化する のに適しています。単純な count の更新では useMemo を使っても意味がありませんが、不要な再計算を防ぐことでパフォーマンスを向上させることができます。
💡 「本当に計算が重いのか?」を考えて useMemo を適用するのがベストプラクティス!
Discussion