はじめに
Reactは、ユーザーインターフェースを作るためのJavaScriptライブラリで、ウェブやモバイルアプリの開発で広く利用されています。Reactを学ぶと、モダンなウェブ開発スキルを身につけることができ、フロントエンドエンジニアを目指す方にとっては欠かせない技術です。
Reactを学ぶ中で、React.memo、useMemo、そしてuseCallbackに触れたことがある方もいるでしょう。これらはいずれも、Reactでパフォーマンスを最適化するための重要なツールですが、それぞれの役割や使う場面が異なります。
初学者の方にとっては、これらの仕組みは少し難しく感じられ、触れる機会もあまりないかもしれません。しかし、Reactプロジェクトでパフォーマンスを向上させる際にはよく使われる技術なので、理解しておくと非常に役立ちます。
この記事では、その3つの中からuseMemoに焦点を当て、詳しく解説します。useMemoは、関数の結果をメモ化し、再レンダリング時に不必要な再計算を避けることができる仕組みです。それでは、一緒に学んでいきましょう。
この記事について
目的
- useMemoの仕組みを理解し、使えるようになること
対象者
- Reactを学び始めた方
- useMemoの理解を深めたい方
Reactの基本シリーズ
Reactを学んでいる中で、「なかなか理解が進まない…」と感じている方もいるのではないでしょうか?そんな方に向けて、Reactの基本をわかりやすく紹介するシリーズ記事を用意しています。
以下はシリーズの一覧です。この記事とあわせて読むことで、より理解が深まると思いますので、ぜひ参考にしてみてください。
-
Reactの基本、コンポーネントを理解する
-
Reactの基本、propsを理解する
-
Reactの基本、useStateを理解しよう
-
Reactの基本、useEffectを理解しよう
-
Reactの基本、useContextを理解しよう
-
Reactの基本、useReducerを理解しよう
-
Reactの基本、useRefを理解しよう
-
Reactの基本、React.memoを理解しよう
React useMemoについて
useMemoは、Reactのフックの一つで、重い計算を効率よく行うために使います。たとえば、大量のデータを扱う場合、何度も同じ計算をしないようにする便利な機能です。
主な仕組みは、関数の結果を保存(メモ化)することで、次回以降、同じ条件で関数が呼び出された場合に、前回の結果をそのまま返し、無駄な再計算を防ぎます。
プログラミングにおけるメモ化(memoization)とは
メモ化とは、一度計算した結果を保存し、同じ入力が与えられたときにその結果を再利用する手法です。これにより、無駄な計算を避けて処理を効率化できます。
たとえば、関数がある引数に基づいて計算を行う場合、最初の呼び出し時にその結果を保存しておき、次回以降に同じ引数で呼び出されたときは再計算せずに保存された結果を返します。これにより、パフォーマンスが向上します。
useMemoの仕組み
useMemoも、このメモ化の考え方を応用しています。具体的には、依存配列の値が変わらない限り、Reactは関数の再計算を行わず、前回の結果をそのまま利用します。これにより、不要な計算を避けてアプリのパフォーマンスが向上します。
通常、Reactコンポーネントは再レンダリングされるたびに関数や変数が再計算されますが、useMemoを使うと、特定の計算結果を保存し、依存するデータが変更されたときだけ再計算を行います。
useMemoの使用目的
useMemoは、次のような場面で役立ちます。
-
重い計算を伴う処理
大量のデータをフィルタリングしたり、複雑な計算を行う場合、結果を保存して再利用することで、再レンダリング時のパフォーマンス低下を防ぎます。
-
データのフィルタリングやソート
フォーム入力に基づいてデータをフィルタリングするなど、入力が変わるたびに処理を行う場合に、useMemoを使って効率化できます。
useMemoの使用に関する注意点
-
常に使う必要はない
useMemoは、すべての場面で必要なわけではありません。軽い計算であれば、使わなくても問題ありません。
-
依存配列を正しく指定する
依存配列が正しく指定されていないと、結果が正しく更新されない、または無駄な再計算が発生することがあります。
依存配列とは
依存配列は、useMemoが再計算を行うタイミングを決めるための「基準となる値の配列」です。Reactは、この配列の中の値が変更されたときだけ、useMemo内の関数を再実行します。
依存配列は、メモ化された値がいつ再計算されるかを決める重要な要素で、フックの第二引数に、関数が依存する変数をリストとして指定します。
使い方と具体的な仕組み
まずは、コンポーネントの冒頭でuseMemoをインポートして、メモ化したい計算処理をラップします。これにより、指定した計算が効率的に実行されるようになります。
import { useMemo } from 'react';
useMemo(() => メモ化したい値を計算する関数, [依存配列]);
メモ化したい値を計算する関数の役割
メモ化したい値を計算する関数は、毎回同じ入力に対して必ず同じ結果を返す純関数である必要があります。この関数は、初回のレンダー時に実行され、結果がメモ化されます。その後、依存配列の値が変わったときにだけ再実行されます。
依存配列の役割
useMemoの依存配列には、関数の結果を再計算するかどうかを判断するために「基準となる値の配列」を指定します。一般的には、以下のような値が依存配列に指定されます。
-
State (状態)
Reactコンポーネント内で管理されているstateの値が依存配列に指定されることがよくあります。stateが変わると、useMemo内の関数が再実行され、新しい計算結果が生成されます。例えば、以下のコードでは
count
が変更されたときだけ関数が再実行されます。const memoizedState = useMemo(() => { return countValue(count); }, [count]);
-
関数の引数として渡す変数
関数をuseMemo内で実行する場合、その引数として渡す変数も依存配列に含める必要があります。以下のコードでは、依存配列に含んだ
num1
とnum2
が変更されると、useMemoが再実行されます。const computeValue = (a, b) => a + b; const memoSum = useMemo(() => { return computeValue(num1, num2); }, [num1, num2]);
依存配列に含まれる値が変わらなければ、Reactは前回の計算結果をキャッシュし、再計算を行いません。依存配列は、あくまで再計算の条件を定義する役割を持っています。
メモ化された値を比較する仕組み
useMemoでメモ化された値を比較する仕組みは、Reactが依存配列内の値の変化を監視していることに基づいています。具体的には、レンダー時に今回渡す依存配列と前回の依存配列を比較し、異なるかどうかを確認します。この比較は「浅い比較(shallow comparison)」と呼ばれ、JavaScriptのObject.is
メソッドによって行われます。
浅い比較では、以下のようなルールに基づいて値が比較されます
-
プリミティブ型の値(数値、文字列、真偽値など)
値そのものが変わったかどうかで判断します。
-
オブジェクトや配列などの参照型の値
参照先(メモリ上の場所)が変わったかどうかで判断します。
依存配列の具体的な比較例
useMemoでメモ化された依存配列がどのように比較されるか、具体的なコードで見てみましょう。
const memoValue = useMemo(() => {
return calculation();
}, [value1, value2]);
この例では、[value1, value2]
が依存配列です。value1
やvalue2
に変化があったときのみ、calculation関数
が再実行され、新しい計算結果がmemoValue
に格納されます。依存配列内の値が前回と同じであれば、calculation
は再実行されず、前回の結果が再利用されます。
プリミティブ型とオブジェクトや配列の比較
useMemoの動きは、値が「プリミティブ型」か「オブジェクトや配列などの参照型」かによって変わります。ここでいうプリミティブ型とは、数値や文字、真偽値(true/false)など、シンプルなデータを指します。これらは、直接その値自体を使って比較されます。
以下のコードでは、value
がプリミティブ型(数値)なので、その値が変わったときだけ再計算が行われます。
// プリミティブ型の例
const value = 1;
const memoizedResult = useMemo(() => value * 2, [value]);
たとえば、このコードではvalueが「1」から「2」に変わった場合、useMemoはもう一度関数を実行して新しい結果を計算します。
次に、「参照型」であるオブジェクトや配列の比較について説明します。この場合、たとえdata
の内容がまったく同じでも、新しいオブジェクトが作成されると、Reactはそれを「別のもの」として認識します。そのため、useMemoは再計算を行います。
// オブジェクトの例
const data = { name: "John" };
const memoizedResult = useMemo(() => processData(data), [data]);
このコードでは、data
がオブジェクトです。たとえば、{ name: "John" }
というオブジェクトが2回連続で同じように見えても、毎回新しく作られると、Reactはそれを別物と見なします。つまり、内容が同じでも、まるで新しいオブジェクトに見えるため、useMemoはもう一度関数を実行してしまいます。
useMemoを使用したコンポーネント例
ここまでuseMemoの基本を学びました。このセクションでは、実際にuseMemoを使った例を紹介します。
以下のコードは、useMemoを使って時間のかかる計算を効率よく処理する方法を説明しています。コードの中で、時間がかかる計算をする関数をシミュレートし、それを使ってuseMemoの効果を確認できるようにしています。
App.jsx
// App.jsx
import React, { useState, useMemo } from 'react';
// 時間のかかる計算関数
// ここでは大量のループを用いて処理が重い状況をシミュレート
const expensiveCalculation = (num) => {
console.log('Calculating...'); // 計算が実行されたときにコンソールに表示
for (let i = 0; i < 1000000000; i++) {} // 時間のかかるループ
return num * 2;
};
// useMemo不使用の子コンポーネント
const WithoutUseMemo = () => {
const [count, setCount] = useState(0); // カウントの状態
const [inputValue, setInputValue] = useState(''); // 入力フィールドの状態
// 時間のかかる計算をそのまま実行
const calculatedValue = expensiveCalculation(count);
return (
<div>
<h2>Without useMemo</h2>
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<p>計算値: {calculatedValue}</p>
</div>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Type here..."
/>
<p>入力値:{inputValue}</p>
</div>
<hr />
</div>
);
};
// useMemo使用の子コンポーネント
const WithUseMemo = () => {
const [count, setCount] = useState(0); // カウントの状態
const [inputValue, setInputValue] = useState(''); // 入力フィールドの状態
// useMemoを使って時間のかかる計算をメモ化
// 依存配列には「count」を指定し、「count」が変わらない限り再計算しない
const calculatedValue = useMemo(() => expensiveCalculation(count), [count]);
return (
<div>
<h2>With useMemo</h2>
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<p>計算値: {calculatedValue}</p>
</div>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Type here..."
/>
<p>入力値:{inputValue}</p>
</div>
</div>
);
};
// 親コンポーネント
const App = () => {
return (
<div>
<WithoutUseMemo />
<WithUseMemo />
</div>
);
};
export default App;
コードの解説
このコード例は以下のようなコンポーネントが作成できます。
このコード例では、useMemoを使った場合と使わない場合の違いを比較できます。2つの子コンポーネント(WithoutUseMemoとWithUseMemo)を使い、useMemoがどのようにパフォーマンスを改善するかを確認しましょう。
-
時間のかかる計算
コードの中の
expensiveCalculation
関数は、数値を2倍にする単純な処理ですが、たくさんのループを使って計算が重くなるようにシミュレートしています。これにより、実際に時間のかかる計算を扱う場合の状況を再現しています。 -
WithoutUseMemoコンポーネント
WithoutUseMemo
では、expensiveCalculation
がcount
の値を基に直接呼び出されます。しかし、inputValue
(入力フィールドの値)が変わっただけでも、Reactはコンポーネント全体を再レンダリングするため、毎回この重い計算が実行されてしまいます。その結果、入力フィールドに文字を入力する際に動作が遅くなり、スムーズに入力できません。
-
WithUseMemoコンポーネント
WithUseMemo
では、同じexpensiveCalculation
をuseMemoフックで包んでいます。これにより、count
の値が変わらない限り、前回の計算結果が保存されて再計算は行われません。つまり、inputValue
が変わっても、重い計算が無駄に実行されないので、入力フィールドの操作がスムーズになります。
このように、useMemoを使用することで、不要な計算を避けてアプリケーションのパフォーマンスを向上させることができます。特に、時間のかかる処理が含まれる場合には、useMemoを適切に使うことで効率的に状態を管理できます。
この記事で学んだこと
この記事を通して、useMemoについて学びました。最後に大切なポイントを振り返りましょう。
useMemoは、Reactで重い計算を効率化するための重要なフックです。同じ計算を何度も繰り返さないように、計算結果をメモリに保存して再利用する仕組みを持ち、コンポーネントのパフォーマンスを向上させます。
特に、大量のデータ処理や複雑な計算が必要な場合、useMemoを使うことで、無駄な再計算を避け、アプリの操作がスムーズになります。
また、useMemoを使う際に重要なのは、依存配列を正しく設定することです。計算結果がいつ更新されるかを判断する基準になるため、依存配列の設定には注意が必要です。
今回の例で紹介したように、useMemoを適切に活用することで、アプリ全体のパフォーマンスを改善できるので、ぜひ実際のプロジェクトでも試してみてください。
参考資料
React公式 - useMemo
https://ja.react.dev/reference/react/useMemo
W3Schools - useMemo
https://www.w3schools.com/react/react_usememo.asp
Wikipedia - メモ化
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.10.19
初心者のためのCORSガイド - サブドメインと脆弱性対策を理解しよう
本記事では、サブドメインやワイルドカードの扱い、no-corsオプションの使い方、そしてCORSに関連する脆弱性について、初心者にもわかりやすく解説します。
- フロントエンド
2024.06.30
Reactの基本、コンポーネントを理解する
フロントエンドエンジニアになりたい、初めてReactを学ぶ方に向けて、この記事ではReactの基本であるコンポーネントについて解説していきます。コンポーネントを学ぶことで、これからのReact学習の理解度が深まります。基本を学び、フロントエンドエンジニアへの道を進んでいきましょう!
- React
2024.09.09
JavaScriptの基本、オブジェクトを理解しよう
JavaScriptを学習する中で、よく目にするオブジェクトについて、少しわかりにくいと感じることはありませんか?実は、JavaScriptのオブジェクトは単なるデータのまとまり以上に、プログラム全体で重要な役割を果たしています。オブジェクトを正しく理解できれば、データの操作や管理がスムーズになり、より効率的にコードを書けるようになります。
- JavaScript
- フロントエンド