[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