useMemo()
useMemo()
とは、関数の結果を保持するためのフックです。
何度計算しても結果が同じ場合、関数による計算の結果をメモ化し、そこから値を取得する。
レンダリングごとに繰り返し行われてしまう不要な再計算をスキップするため、パフォーマンスの向上が期待できます。
useMemo()の基本構文
// useMemo()でメモ化したい関数の実行結果をラップする
// useMemo = (() => 値を計算する関数の呼び出し, [値の計算に必要な要素の配列]);
// 第2引数に空の依存配列を渡した場合、初回のみ実行され、2回目以降のレンダリング時には
// キャッシュから値(関数を実行した結果の値)を取得する(初回のレンダリング時の計算結果を使い回す)
const sampleMemoFunc = useMemo(() => doSomething(a, b), [])
// 第2引数の依存配列が空ではない場合、要素の値に変更あった場合のみ関数を再実行する
const sampleMemoFunc = useMemo(() => doSmothing(a, b), [a, b])
useMemo()とuseCallback()との違い
useMemo()
は関数結果をメモ化しますが、useCallback()
は関数自体をメモ化する
サンプルコード解説
useMemo()を用いたサンプルコードを作成しました。
「A+1 ボタン」を押すとAのカウントが1ずつ増えていきます。
「B+1 ボタン」を押すとBのカウントが1ずつ増えていきます。
また、画面上部にある正方形の面積はカウントB×カウントBで求めることができます。正方形の面積はカウントAの値には依存せず、カウントBの値によってのみ決定されます。
index.js
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App />);
App.js
// useMemoを利用するためにimportする
import React, { useState, useMemo } from "react";
import "./styles.css";
// 正方形の面積を求める関数calculateSquareを定義
const calculateSquare = (countB) => {
console.log("カウントBが更新され、正方形の面積が計算されました");
return countB * countB;
};
const Counter = () => {
// カウントAの現在の状態変数countA、countAを更新する関数setCountAを定義
const [countA, setCountA] = useState(0);
// カウントBの現在の状態変数countB、countBを更新する関数setCountBを定義
const [countB, setCountB] = useState(0);
// カウントAの数を1ずつ増加させる関数countIncrementAを定義
const countIncrementA = () => {
setCountA((prevCount) => prevCount + 1);
console.log("A+1 ボタンが押されました");
};
// カウントBの数を1ずつ増加させる関数countIncrementBを定義
const countIncrementB = () => {
setCountB((prevCount) => prevCount + 1);
console.log("B+1 ボタンが押されました");
};
// useMemo()でラップして、計算結果をメモ化する
// 正方形の面積を求める関数calculateSquareを実行する
// 引数にはcountBを渡す(Counterコンポーネント外で定義しているため引数が必要)
// 関数calculateSquareの戻り値を受け取る
// 第2引数にはcountBを渡しているためcountAが更新されても、
// countBが更新されなければ、関数calculateSquareは実行されない
const squareArea = useMemo(() => calculateSquare(countB), [countB]);
// useMemo()でラップしない場合以下のようになる
// const squareArea = calculateSquare(countB);
return (
<>
<h3>正方形の面積: {squareArea}</h3>
<div>
<p>カウントA: {countA}</p>
// ボタンが押されるたび、カウントAを1ずつ増やす
<button onClick={countIncrementA}>A+1 ボタン</button>
</div>
<div>
<p>カウントB: {countB}</p>
// ボタンが押されるたび、カウントBを1ずつ増やす
<button onClick={countIncrementB}>B+1 ボタン</button>
</div>
</>
);
};
export default function App() {
return <Counter />;
}
コンソール画面を確認すると、「A+1 ボタン
」を押した場合、「A+1 ボタンが押されました
」と表示される。「B+1 ボタン
」を押した場合、「B+1 ボタンが押されました
」と「カウントBが更新され、正方形の面積が計算されました
」が表示される。
これは「A+1 ボタン」が押された際にstate
が変更され、Counter
コンポーネントの再レンダリングが行われる。その際に、useMemo()
でラップされた関数calculateSquare
はcountB
にのみ依存しているため、メモ化された計算結果を取得して表示しまう。
useMemo()
でラップしない場合、「A+1 ボタン」が押された際にstate
が変更され、Counter
コンポーネントの再レンダリング行われたタイミングで関数calculateSquare
が実行される。
styles.css
記述なし
一人言メモ
-
画面のレンダリングが行われた場合、関数(
{}
がついてる)は呼び出し元がない場合は実行されない。(Javaと同じ) - 画面のレンダリングが行われた場合、関数ではない(useMemo()、map()...etc)は実行される。(変数に代入しているのと同じ?)