1. ホーム
  2. 記事一覧
  3. Reactの基本、React.memoを理解しよう

2024.10.17

Reactの基本、React.memoを理解しよう

はじめに

Reactは、ユーザーインターフェースを構築するためのJavaScriptライブラリで、近年、ウェブやモバイルアプリの開発現場で広く利用されています。

Reactを学ぶ中で、React.memo、useMemo、そしてuseCallbackに触れたことがある方もいるでしょう。これらはいずれも、Reactでパフォーマンスを最適化するための重要なツールですが、それぞれの役割や適用場面が異なります。

初学者の方にとっては、これらの仕組みは少し難しく感じられ、触れる機会もあまりないかもしれません。しかし、Reactプロジェクトでパフォーマンスを向上させる際にはよく使われる技術なので、理解しておくと非常に役立ちます。

この記事では、その3つの中からReact.memoに焦点を当て、詳しく解説します。React.memoは、コンポーネントの再レンダリングを防ぐための仕組みです。それでは、一緒に学んでいきましょう。

この記事について

目的

  • React.memoの仕組みを理解し、使えるようになること

対象者

  • Reactを学び始めた方
  • React.memoの理解を深めたい方

React Hooksシリーズ

Reactを学んでいる方の中で、React Hooksについて理解が進まない、どの場面で使うのかよく分からない、と感じる方もいるのではないでしょうか?そんな方々向けに、React Hooksをシリーズで紹介しています。

以下はReact Hooksシリーズの記事一覧です。この記事とあわせて読むことで、より理解が深まりますのでぜひご覧ください。

React.memoについて

React.memoは、コンポーネントの再レンダリングを防ぐための仕組みです。これを活用することで、不要な再レンダリングを減らし、Reactアプリケーションのパフォーマンスを向上させることができます。 この仕組みを正しく理解するには、まず「メモ化(キャッシュ化)」の概念を押さえておくことが重要です。

プログラミングにおけるメモ化(memoization)とは

メモ化とは、同じ計算を繰り返さないように一度実行した結果を保存し、同じ入力が与えられたときに保存した結果を再利用する手法です。

たとえば、関数がある引数に基づいて計算結果を返すプログラムがあったとします。最初の呼び出し時にその結果を保存しておき、次回以降、同じ引数で呼び出された場合は、再計算せずに保存した結果を返します。このように、効率的に計算結果を再利用することで、処理のパフォーマンスを向上させることができます。

React.memoの仕組み

React.memoもこのメモ化の考え方を応用しています。具体的には、親コンポーネントから渡されるpropsが変わらない限り、Reactはそのコンポーネントを再レンダリングしません。これにより、不要な再レンダリングを防ぎ、アプリケーションのパフォーマンスを改善します。

通常、親コンポーネントが再レンダリングされると、子コンポーネントも自動的に再レンダリングされます。しかし、子コンポーネントのpropsが変わらないにもかかわらず再レンダリングされるのは非効率的です。このような場合にReact.memoを使うと、前回のレンダリング結果が再利用され、再レンダリングがスキップされます。

ReactのReact.memoは、コンポーネントに渡されるpropsの内容を比較して動作します。親から渡されたpropsが全く同じ内容の場合、React.memoは「このコンポーネントを再レンダリングする必要はない」と判断します。これが「メモ化」と呼ばれる仕組みで、同じpropsの時は「前回の結果を使おう」とする考え方です。これにより、無駄な再レンダリングを減らし、アプリのパフォーマンスを向上させます。

React.memoの使用に関する注意点

React.memoは、すべてのケースで使用するのが適切というわけではありません。特に、頻繁に再レンダリングされるコンポーネントや、大量のデータを表示するコンポーネントで効果を発揮します。しかし、軽量なコンポーネントや頻繁な更新の必要がない場合、React.memoを使うことでかえってコードが複雑になり、メリットが薄れることがあります。

補足情報

  • 純粋関数コンポーネントに対して効果を発揮

    React.memoは、純粋関数コンポーネントに対してのみ効果を発揮します。

    純粋関数コンポーネントとは、同じpropsが与えられると、常に同じ結果を返すコンポーネントのことです。例えば、ユーザーの名前やプロフィール画像など、「表示するだけ」のコンポーネントなどです。

  • stateやcontextがある場合の制約

    コンポーネント内でstateやcontextを使用する場合、その値が変わるとReact.memoは無効になり、再レンダリングが発生します。これは、stateやcontextの変更がコンポーネント内部で発生し、propsだけの比較では管理できないからです。

  • 関数やオブジェクトを渡す場合

    React.memoでメモ化されたコンポーネントのpropsに関数やオブジェクトを渡すと、毎回新しいインスタンスが生成され、propsが変更されたと判断されます。これにより、React.memoの効果が失われます。関数やオブジェクトをpropsとして渡す場合は、毎回新しいインスタンスが生成されないように、useCallbackやuseMemoを使って最適化することが推奨されます。

React.memoの使用目的

主に、次のようなケースで使用されます

  • 大量のデータを保持するコンポーネント

    たとえば、長いリストや複雑なUI要素をレンダリングする際に、無駄な再レンダリングを防ぐために有効です。

  • 頻繁に再レンダリングされるコンポーネント

    親コンポーネントが頻繁に更新される場合に、その子コンポーネントをメモ化し、親コンポーネントのレンダリングの影響を受けないようにします。

使い方と具体的な仕組み

React.memoを使うためには、コンポーネントの冒頭でmemoをインポートし、対象のコンポーネントをmemoでラップします。これにより、そのコンポーネントに渡されるpropsがメモ化され、前回のpropsと比較して同じであれば再レンダリングをスキップする仕組みが作られます。

import { memo } from 'react';

const MyComponent = memo(function SomeComponent(props) {
  // コンポーネントの内容
});

メモ化の判断に使われるObject.isメソッド

React.memoでは、propsに変更があったかどうかをJavaScriptのObject.isメソッドで判断します。 Object.isは、2つの値が「完全に同じかどうか」を厳密に比較するためのメソッドです。この比較によって、propsが前回と同じであれば再レンダリングをスキップし、異なる場合のみ再レンダリングを実行します。

比較する値は、プリミティブ型(文字列、数値、ブール値など)と、オブジェクトや配列の参照がチェックされます。ただし、オブジェクトの場合は「中身」ではなく「参照」が同じかどうかで比較される点に注意が必要です。

React.memoが効果を発揮する例

以下の例では、propsに変更がないと判断され、再レンダリングはスキップされます。

const MyComponent = memo(function MyComponent(props) {
  console.log('レンダリングされました');
  return <div>{props.text}</div>;
});

// 初回レンダリング時のprops
<MyComponent text="hello" count={1} />

// 再レンダリング時(同じpropsを渡す)
<MyComponent text="hello" count={1} />

この例では、2回目のレンダリング時にtextcountの値が前回と同じです。Object.isで比較した結果、両方のpropsが同一とみなされ、再レンダリングはスキップされます。

React.memoが効果を発揮しない例

次に、propsにオブジェクトを渡した場合の例を見てみましょう。

const data = { id: 1 };
<MyComponent data={data} />

// 再レンダリング時に新しいオブジェクトを渡す
<MyComponent data={{ id: 1 }} />

dataの中身はどちらも{ id: 1 }で同じように見えますが、異なるオブジェクトのインスタンスが渡されています。JavaScriptでは、オブジェクトの比較は参照の一致で行われるため、Object.isはこれらを異なるものと判断し、再レンダリングが発生します。

ここまでのまとめ

React.memoは、propsが同じ場合に前回のレンダリング結果を再利用することで、不要な再レンダリングを防ぎ、パフォーマンスを向上させます。ただし、オブジェクトや配列をpropsとして渡す場合は注意が必要です。新しいインスタンスが生成されると同じ内容でも「異なる」と判断されるため、useCallbackやuseMemoを使って参照の再利用を意識することが推奨されます。

React.memoを使用したコンポーネント例

ここまでReact.memoの基本を学びました。このセクションでは、React.memoを使用したコンポーネント例を紹介します。

以下は、React.memoを使った具体的なコード例です。親コンポーネントから渡されるpropsが同じであれば、再レンダリングがスキップされることを確認できます。コード例を見ながら、どのようにメモ化が機能するか理解を深めましょう。

親コンポーネント(App.jsx)

// App.jsx

import React, { useState } from 'react';
import MyComponent from './MyComponent'; // 子コンポーネントをインポート

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('Hello');

  return (
    <div>
      <h1>React.memoの使用例</h1>

      {/* カウントを増加 */}
      <button onClick={() => setCount(count + 1)}>
        カウント: {count}
      </button>

      {/* テキストの変更 */}
      <button onClick={() => setText(text === 'Hello' ? 'こんにちは' : 'Hello')}>
        テキスト変更
      </button>

      {/* メモ化されたコンポーネント */}
      <MyComponent text={text} />
    </div>
  );
}

export default App;

子コンポーネント(MyComponent.jsx)

// MyComponent.jsx

import React from 'react';

// React.memoでラップし、同じpropsでの再レンダリングを防止
const MyComponent = React.memo(({ text }) => {
  console.log('MyComponentがレンダリングされました');
  return <p>テキスト: {text}</p>;
});

export default MyComponent;

コードの解説

このコード例は以下のようなコンポーネントが作成できます。

  • 親コンポーネント (App.jsx)

    このコンポーネントでは、ボタンをクリックすることでcounttextの値を変更できます。countは親コンポーネント内でのみ管理され、子コンポーネントMyComponentには渡されていません。そのため、countの値が変更されてもMyComponentの再レンダリングは発生しません。

  • 子コンポーネント (MyComponent.jsx)

    このコンポーネントはReact.memoでラップされており、渡されたpropsが前回と同じであれば、Reactは再レンダリングをスキップします。propsに変更があった場合のみ再レンダリングが行われるため、textが更新されると新しい内容が表示されます。一方で、countのようにpropsとして渡されていない値の変更は、このコンポーネントに影響を与えません。

このように、React.memoを使わないと、親コンポーネントの再レンダリングにより、propsが変わらなくても子コンポーネントが再レンダリングされます。React.memoを使うことで、propsに変化がないときの無駄な再レンダリングを防ぎ、パフォーマンスを向上させることができます。

この記事で学んだこと

この記事を通して、Reacr.memoについて学びました。最後に大切なポイントを振り返りましょう。

React.memoは、コンポーネントの再レンダリングを防ぐ仕組みで、不要な再レンダリングを減らし、Reactアプリケーションのパフォーマンスを向上させるために活用します。

プログラミングにおけるメモ化とは、同じ計算を繰り返さないように一度実行した結果を保存し、同じ入力が与えられたときに保存した結果を再利用する手法です。

React.memoは、このメモ化の考え方を応用し、親コンポーネントから渡されるpropsが変わらない限り再レンダリングをスキップします。これにより、パフォーマンスが改善され、効率的なアプリケーション開発が可能になります。

参考資料

React公式 - useMemo, Memo

https://ja.react.dev/reference/react/memo

Wikipedia - メモ化

https://ja.wikipedia.org/wiki/メモ化

MDN web docs - Object.is()

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/is

【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

「フリーランスエンジニア」

近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

「成功する人とそうでない人の違いは何か?」

私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

note記事3000いいね超えの殿堂記事 今すぐ読む

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事

2020.02.25

完全未経験からエンジニアを目指す爆速勉強法

USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

  • キャリア・学習法
  • エンジニア

2024.11.14

React初心者必見!コンポーネント・状態管理・Hooksをわかりやすく解説するまとめ記事

この記事では、エンベーダーで紹介してきたReactの3つの基本について、要点をまとめて紹介します。また、各技術に関する詳細な解説記事のリンクも掲載しています。この記事を読むことで、Reactの基本的な学習範囲の全体が分かり、各技術の要点を簡単に把握し、リンク先で詳細を学べるため、効率よく学習を進められます。そして、次に何を学ぶべきかが見えてくるでしょう。

  • React
  • フロントエンド

2024.10.19

初心者のためのCORSガイド - サブドメインと脆弱性対策を理解しよう

本記事では、サブドメインやワイルドカードの扱い、no-corsオプションの使い方、そしてCORSに関連する脆弱性について、初心者にもわかりやすく解説します。

  • フロントエンド

2024.10.22

Reactの基本、useMemoを理解しよう

この記事では、useMemoに焦点を当て、詳しく解説します。useMemoは、関数の結果をメモ化し、再レンダリング時に不必要な再計算を避けることができる仕組みです。それでは、一緒に学んでいきましょう。

  • React
  • フロントエンド