第82章:React.memo
パフォーマンス系の話だけど、やること自体はけっこうシンプルなので、リラックスしてついてきてね 🧋
82-1 🎯 今日のゴール
この章のゴールはざっくりこの3つです:
React.memoが「何をしてくれる道具」なのか説明できる- TypeScript つきのコンポーネントに、自然な形で
React.memoをつけられる - 「どんなときに使うとおいしいか」をイメージでつかめる
82-2 💡 React.memo ってなに?
一言で言うと:
「同じ Props なら、そのコンポーネントもう一回描かなくていいよ〜」って React に教えるオプション
です。
通常、React は
- 親コンポーネントが再レンダリング ↓
- その下にぶら下がっている子コンポーネントたちも、基本ぜんぶ再レンダリング
という動きをします。
そこで memo(React.memo) の出番。
コンポーネントを memo で包むと、
- Props が前回と同じなら → 再レンダリングをスキップ
- Props が変わっていれば → ちゃんと再レンダリング
みたいな動きをしてくれます。(React)
📝 公式ドキュメントでも「
memoはパフォーマンス最適化のためのオプションであり、『動く・動かない』を決めるものではない」と書かれています。(React)
82-3 🧪 まずは「メモ化なし」のコンポーネント
小さなサンプルで、memo あり・なしの違いを見てみましょう 👀
👇 やりたいこと
- 親コンポーネント:カウンター + 名前入力
- 子コンポーネント:
nameだけを表示する「自己紹介カード」
カウンターだけ増やしているのに、自己紹介カードも毎回レンダリングされちゃう様子を console.log で観察します。
App.tsx(まだ memo なし)
import { useState } from "react";
type ProfileProps = {
name: string;
};
function Profile({ name }: ProfileProps) {
console.log("👧 Profile がレンダリングされました");
return <p>こんにちは、{name} さん!</p>;
}
export function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("りん");
console.log("🧑💻 App がレンダリングされました");
return (
<div style={{ padding: "1rem" }}>
<h1>React.memo の練習 ✨</h1>
<section style={{ marginBottom: "1rem" }}>
<h2>カウンター</h2>
<p>クリック回数: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>
1 増やす
</button>
</section>
<section style={{ marginBottom: "1rem" }}>
<h2>名前入力</h2>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="お名前を入力"
/>
</section>
<section>
<h2>自己紹介カード</h2>
<Profile name={name} />
</section>
</div>
);
}
この状態で
- カウンターのボタンだけポチポチすると…
👉 コンソールには App も Profile も毎回ログが出る はずです。
「
name変えてないのにProfileまで動くの、ちょっともったいないよね?」 → ここをReact.memoで改善していきます 🚀
82-4 🧊 React.memo で子コンポーネントを「おとなしく」させる
✅ 基本の書き方(TypeScript 版)
import { memo } from "react";
type Props = { /* ここは普通に props の型 */ };
const MyComponent = memo(function MyComponent(props: Props) {
// ...
});
-
memoは 元のコンポーネントを変更しない で、- 「メモ化された新しいコンポーネント」を返してくれます。(React)
-
TypeScript 的には、
Propsの型がちゃんと引き継がれる ので相性バッチリです 💘
🔁 さっきの Profile をメモ化してみる
Profile を memo で包んでみましょう。
import { useState, memo } from "react";
type ProfileProps = {
name: string;
};
const Profile = memo(function Profile({ name }: ProfileProps) {
console.log("👧 Profile がレンダリングされました(memo 版)");
return <p>こんにちは、{name} さん!</p>;
});
export function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("りん");
console.log("🧑💻 App がレンダリングされました");
return (
<div style={{ padding: "1rem" }}>
<h1>React.memo の練習 ✨</h1>
<section style={{ marginBottom: "1rem" }}>
<h2>カウンター</h2>
<p>クリック回数: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>
1 増やす
</button>
</section>
<section style={{ marginBottom: "1rem" }}>
<h2>名前入力</h2>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="お名前を入力"
/>
</section>
<section>
<h2>自己紹介カード</h2>
{/* 書き方はそのまま */}
<Profile name={name} />
</section>
</div>
);
}
この状態で
-
「カウンターだけ」ポチポチする
- 👉 コンソールには App だけログが出て、Profile のログは出なくなる はず
-
「名前だけ」変える
- 👉
Profileのログもちゃんと出る(Props のnameが変わったから)
- 👉
これが React.memo の基本的な働きです ✨
82-5 🧠 どんなときに React.memo を使う?
memo は「とりあえず全部につける」ものじゃなくて、ここぞ!というところだけ 使うのがポイントです。
🍰 向いている場面
- 親コンポーネントがよく再レンダリングされる
- でもその子コンポーネントの Props はあまり変わらない
- しかもその子コンポーネントの処理が ちょっと重い(リストが大きい・計算が多い など)
たとえば:
- 大量のカードを並べる「検索結果リスト」の 各アイテムコンポーネント
- グラフ・チャートなど、描画コストが高いコンポーネント
- 「見た目だけ」の表示専用コンポーネント(Props が変わらないかぎり同じ表示)
82-6 ⚠️ React.memo の注意ポイント
① 状態・Context には効かないよ
memo が見ているのは あくまで「Props」だけ です。(React)
- 子コンポーネント自身が
useStateを持っていて state が変わった useContextで読んでいる Context の値が変わった
こういうときは、React.memo していても普通に再レンダリング されます。
② オブジェクト・配列・関数 Props は要注意
memo は、Props を 「浅く比較(shallow compare)」 しています。(React)
{ foo: 1 }みたいなオブジェクト[]や["a", "b"]みたいな配列() => {}みたいな関数
これらは 「中身が同じでも、毎回新しく作り直していると別物扱い」 になります。
なので
<Profile options={{ showBorder: true }} />
みたいに JSX の中でオブジェクトを新しく作る書き方 をしていると、
- 毎回
optionsが「新しいオブジェクト」と見なされる - →
React.memoが効かなくなる
という落とし穴があります(このあたりは次の 83章・84章で useCallback などと一緒に見ていきます 🌟)。
82-7 🧬 React 19 と React Compiler の関係
React 19 以降は、React Compiler という「自動でいい感じに最適化してくれるコンパイラ」が入ってくる予定で、
「
React.memoやuseMemo/useCallbackをあまり意識しなくてもよくなるよ〜」
という方向に進んでいます。(React)
でも、
- すべてのプロジェクトで React Compiler が すぐに有効になるわけではない
- 既存のコードやライブラリには、まだまだ
React.memoがたくさん出てくる - 一部のケースでは、自分で
memoを使ったほうがいい場面 も残る
ので、
「
React.memoの考え方を知っておく」ことは、まだまだとても大事
というスタンスで覚えておくと安心です 💪
82-8 🧭 Mermaid 図でイメージしてみよう
React.memo がある場合 / ない場合のイメージを図にしてみます 🗺️
(graph TD は「上 → 下」の矢印の図、くらいに思っておけばOKです)
- 左側:普通の子 → 親が動くたびに 一緒にレンダリング
- 右側:
React.memo子 → Props が変わったときだけ レンダリング
というイメージです ✨
82-9 📝 ミニ練習問題
軽いアウトプットで、理解を定着させておきましょ〜 🐣
練習1:アイコンだけのボタンをメモ化してみる
-
IconButton.tsxというコンポーネントを作る- Props:
label: string/onClick: () => void
- Props:
-
App.tsxからonClickを渡して表示する -
最初は
React.memoなしで作って、 -
そのあとで
memoを import して、IconButtonをメモ化してみる
「書き方が分からなかったら、さっきの
Profileのコードをコピペして形だけマネしてOKです👌」
練習2:console.log で「本当に減ってるか」確認してみる
-
IconButtonの中でconsole.log("IconButton レンダリング")を仕込む -
親で「別の state(例:カウンター)」を増やすボタンを追加
-
そのカウンターだけをポチポチしてみて、
React.memo前:IconButtonのログが毎回出るReact.memo後:IconButtonのログが出なくなる(はず!)
82章 まとめ 🍵
-
React.memoは 「同じ Props なら再レンダリングしないでOK」 というパフォーマンス向上テク -
TypeScript とは相性よくて、普通に Props の型をつけた関数コンポーネントを
memoで包むだけ でOK -
ただし、
- state や Context の変化には効かない
- オブジェクト・配列・関数 Props は「毎回新しく作らない」工夫が必要
-
React 19 + React Compiler 時代でも、
React.memoの考え方はまだまだ大事 💡
次の 83章では、
「なんで関数やオブジェクトを Props に渡すと React.memo が効きにくいの?」
というところを、useCallback とセットで見ていきます 🧠⚡️