shiftB
instagramyoutube
運営会社プライバシーポリシー特定商取引法に基づく表記JavaScript学習サイト JS Gym
お問い合わせ
©2025 bubekichi inc.

Reactでのレンダリング制御の基本:useCallback・useMemo・React.memoの使い方と挙動理解

0
XThreads
受講生ブログ

Reactでのレンダリング制御の基本:useCallback・useMemo・React.memoの使い方と挙動理解

icon
kento

Reactでのレンダリング制御の基本:useCallback・useMemo・React.memoの使い方と挙動理解

投稿日: 2025年11月12日

Tips
学習振り返り
要約
  • ReactのuseCallback、useMemo、React.memoを使って不要な再レンダリングを制御する方法を解説。
  • useCallbackは関数をメモ化し、useMemoは重い計算処理やオブジェクト・配列のメモ化に使われる。
  • React.memoは親コンポーネントの再レンダリングによる子コンポーネントの不要な再レンダリングを防ぐ役割がある。
音声で記事を再生

 【はじめに】

なんか急にレンダリングの制御の挙動を理解してみたいなっていうのをふと思ったので、
実証も兼ねてuseCallback・useMemo・React.memoあたりの基本的な使い方と、どんな挙動になるのかをみていきたいと思います!

※間違っていたら、コメントいただけると幸いです笑
あと、内容が長めなので途中で読むのやめたくなる可能性があります笑

まずは、useCallback・useMemo・React.memoそれぞれどんな特徴でどんな時に使うのかを基本的な内容になりますが紹介していきます!🗿🗿🗿🗿🗿🗿

【useCallback】

関数処理にあてるレンダリング制御として使う。
関数をメモ化(記憶)することで、不要なレンダリングを制御する。
親側にある関数処理にuseCallbackを使い、子がその関数を受け取りReact.memoを使って、レンダリングを制御する。

【useMemo】

カスタムフックによる重い計算処理、オブジェクトまたは配列での計算処理に活用する。

例:filterやmapによる配列処理で、毎回実行するのを防ぐ。


中身が同じでも、毎回新しいオブジェクトや配列の参照を固定するのに使う。
React.memoを使った子コンポーネントにオブジェクトや配列に渡す際、propsが変わっていないことを正しく認識させるため。

【React.memo】

受け取ったpropsや関数をレンダリング制御に活用する。

例えば複数のPropsを親から子コンポーネントが受け取った場合、前回渡された時と全てが同一のもの(インスタンス)であれば、
親が再レンダリングされても、Propsを受け取った子コンポーネントの再レンダリングはスキップされます。

逆に、複数のpropsの中で一つでも再レンダリングによって、前回と内容が違ったらコンポーネント全体の再レンダリングが行われるってことです。

【React.memoの挙動】

子コンポーネントに複数のPropsを渡した場合

  1. 複数のPropsを親から子コンポーネントが受け取り、前回渡された時と全てが同一だった場合

  • props.count が前回と 同一 (===)

  • props.text が前回と 同一 (===)

  • props.onCountClick が前回と 同一 (===)

  • 全てのPropsが 同一 (===) だった

結果: 「全く変更なし」と判断し、再レンダリングをスキップする

  1. 逆に、複数のpropsの中で1つでも再レンダリングによって、前回と内容が違ったらコンポーネント全体の再レンダリングが行われた場合

  • props.count が前回と 同一 (===)

  • props.text が前回と 不一致(!==) (ここで不一致を発見!)

  • props.onCountClick はもう比較さえしない

    • 結果: 「変更あり」と判断し、コンポーネント全体を再レンダリングする。

【Props毎にレンダリング制御する対策】

以下の対策はあくまでケースバイケースが前提となります。

Props毎にレンダリング制御するなら、コンポーネントを小さく分割、それぞれの子コンポーネントが受け取るPropsを最小限にし、React.memoでレンダリング制御する。

【例1 使い方(useCallbackとReact.memo)】

useCallback, React.memoのある場合のコード

// 親コンポーネント

"use client";

import { useState, useCallback } from "react";
import { CountComponent } from "./component/button/CountComponent";
import "./globals.css";

const MyComponent = () => {
  const [count, setCount] = useState(0);

  // ★ 効果を実証するための子に「無関係な」State
  const [text, setText] = useState("");

  console.log("親コンポーネントが再レンダリング");

  // 2. この関数は子コンポーネントに渡される
  const handleCountClick = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return (
    <div className="absolute inset-0 flex flex-col items-center justify-center gap-10 bg-white text-black font-bold">
      <div>
        <label>親のState(子には無関係): {text}</label>
        <input
          type="text"
          value={text}
          onChange={(e) => setText(e.target.value)}
          className="border p-2"
        />
      </div>

      {/* React.memo された子に渡すからこそ useCallback が活きる */}
      <div className="">
        <CountComponent count={count} onCountClick={handleCountClick} />
      </div>
    </div>
  );
};
export default MyComponent;

// 子コンポーネント

"use client";

import React from "react";
import "../../globals.css";

type CountProps = {
  count: number;
  onCountClick: () => void;
};

const ChildComponent = ({ count, onCountClick }: CountProps) => {
  // このログが出るかどうかが鍵
  console.log("--- 子コンポーネントが再レンダリングされました ---");

  return (
    <div>
      <p className="mb-10 text-center text-2xl">{count}</p>
      <button
        type="button"
        className="border-3 rounded-2xl py-2 px-4 bg-blue-400 text-2xl"
        onClick={onCountClick}
      >
        カウントボタン🗿
      </button>
    </div>
  );
};

export const CountComponent = React.memo(ChildComponent);

パターン1:「カウントボタン🗿」を押す

  • count が変わります。

  • 親が再レンダリングされます。

  • 子の count Propsが変わったので、子は再レンダリングされます。

  • コンソールログ:

    親コンポーネントが再レンダリング
    --- 子コンポーネントが再レンダリングされました ---

パターン2:「親のState(子には無関係)なテキスト」入力欄に文字を入力する(ここが最重要!)

  • text Stateが変わり、親コンポーネントだけが再レンダリングされます。

  • console.log("親...") が出力されます。

  • React.memo が子のPropsを比較します。

    • count: 変わっていません。

    • onCountClick: useCallback のおかげで変わっていません(=前回と同一のインスタンス)。

  • React.memo は「全てのPropsが変わっていない」と判断し、子の再レンダリングをスキップします

    • 子は、textpropsを受け取っていなのでtextデータを知りません。なので、 「全てのPropsが変わっていない」と判断。

  • コンソールログ:

親コンポーネントが再レンダリング
(★子のログは出力されない!)

【エビデンス】


useCallback, React.memoの無い場合のコード

// 親コンポーネント

"use client";

import { useState } from "react";
import { CountComponent } from "./component/button/CountComponent";
import "./globals.css";

const MyComponent = () => {
  const [count, setCount] = useState(0);

  // ★ 効果を実証するための「無関係な」State
  const [text, setText] = useState("");

  console.log("親コンポーネントが再レンダリング");

  // 2. この関数は子コンポーネントに渡される
  const handleCountClick = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <div className="absolute inset-0 flex flex-col items-center justify-center gap-10 bg-white text-black font-bold">
      <div>
        <label>親のState(子には無関係): {text}</label>
        <input
          type="text"
          value={text}
          onChange={(e) => setText(e.target.value)}
          className="border p-2"
        />
      </div>
      <div className="">
        <CountComponent count={count} onCountClick={handleCountClick} />
      </div>
    </div>
  );
};
export default MyComponent;

// 子コンポーネント

"use client";

import "../../globals.css";

type CountProps = {
  count: number;
  onCountClick: () => void;
};

export const ChildComponent = ({ count, onCountClick }: CountProps) => {
  // このログが出るかどうかが鍵
  console.log("--- 子コンポーネントが再レンダリングされました ---");

  return (
    <div>
      <p className="mb-10 text-center text-2xl">{count}</p>
      <button
        type="button"
        className="border-3 rounded-2xl py-2 px-4 bg-blue-400 text-2xl"
        onClick={onCountClick}
      >
        カウントボタン🗿
      </button>
    </div>
  );
};

コードの流れ!!(useCallback も React.memo も【ない】場合)

パターン1:「カウントボタン🗿」を押す

  • count が変わります。

  • 親コンポーネント(MyComponent)が再レンダリングされます。

  • 子コンポーネント(CountComponent)に渡される count Propsが変わったので、子は(正しく)再レンダリングされます。

コンソールログ:

親コンポーネントが再レンダリング
--- 子コンポーネントが再レンダリングされました ---

(※このパターンは、【ある】場合と全く同じ結果になります)

パターン2:「親のState(子には無関係)なテキスト」入力欄に文字を入力する(ここが最重要!)

  • text Stateが変わり、親コンポーネント(MyComponent)だけが再レンダリング。

  • console.log("親...") が出力。

  • 親の再レンダリングに伴い、handleCountClick 関数が新しく生成(useCallback がないため)

  • 子コンポーネント(CountComponent)は React.memo で守られていません。

  • Reactのデフォルトのルール(親が再レンダリングしたら、子も再レンダリングする)が適用。

  • 子は、count Propsが変わっていなくても、親に**"つられて"無条件に再レンダリング**される。

コンソールログ:

親コンポーネントが再レンダリング
--- 子コンポーネントが再レンダリングされました ---  <-- ★不要な再レンダリングが発生!

結論: 「無関係なtext」を操作しただけで、CountComponent のログが出力されてしまうこと(=不要な再レンダリング)が確認できます。
 これは、React.memoがいないため、親の再レンダリングがそのまま子に伝播してしまうためです。

【エビデンス】



以下、パターンによる挙動の違いのまとめ(useCallbackとReact.memoの例)

パターンA:両方【ない】(React.memo も useCallback もない)

  1. 子(React.memo なし): 比較機能がありません。

  2. 結果: 親が再レンダリングされたので、子は無条件に再レンダリングされます。

パターンB:React.memo【だけ】ある

  1. 親(useCallback なし): handleCountClick が新しい関数として生成されます。

  2. 子(React.memo あり): Propsを比較します。

    • count: 変わってない (OK)

    • onCountClick: 新しい関数が来た! (NG)

  3. 結果: 「Propsが変わった」と誤認し、再レンダリングされます。

パターンC:useCallback【だけ】ある

  1. 親(useCallback あり): handleCountClick は前回と同一の関数が準備されます。

  2. 子(React.memo なし): 比較機能がありません。

  3. 結果: 比較されず、親につられて無条件に再レンダリングされます。

パターンD:両方【ある】(理想)

  1. 親(useCallback あり): handleCountClick は前回と同一の関数が準備されます。

  2. 子(React.memo あり): Propsを比較します。

    • count: 変わってない (OK)

    • onCountClick: 前回と同一の関数が来た! (OK)

  3. 結果: 「全てのPropsが変わっていない」と正しく判断し、再レンダリングをスキップします!

現在React19で発表された、React Compiler でuseMemo、useCallback、React.memo による手動メモ化を不要にしてくれる!!

【終わり】

両方使うことで力が発揮される!!!🗿🗿🗿🗿🗿🗿🗿🗿

0

シェア!

XThreads
icon
kento
プロフィールを見る
Loading...
記事一覧に戻る
XThreads
0

関連記事

TAの使い方とマインドセット

icon
tomoe

MBTI×学習スタイル 巨匠の私が約4ヶ月で完走した学習戦略

user
吉本茜

【実録Q&A】初学者に伝授したデバッグ思考の体系化

user
吉本茜

AIが教えてくれるけど理解するのは自分です〜

user
吉本茜

まぁええか精神の注意点

user
吉本茜

みんな真面目過ぎん?まぁええか精神のすすめ

user
吉本茜

最新記事

React useEffect:基本的な内容から「不要なパターン」

icon
kento

課題6完了!

user
小松崎鉄雄

動作に支障ない401エラーと向き合ったらNext.jsの仕様にたどり着いた話

user
吉本茜

【実録Q&A】型をキメて書くとあと楽ですよ〜な話

user
吉本茜

【実録Q&A】初学者に伝授したデバッグ思考の体系化

user
吉本茜

すぐできる!レビュワーへの気遣い💐

user
吉本茜