メモ化
Reactはstate
やprops
が更新される度に再レンダリングが行われます。
useCallback()
、React.memo()
、useMemo()
を利用すると、初回の処理を実行して記録し、値が必要となった2回目以降は、保持していた計算結果を再利用します。これをメモ化といいます。
メモ化は再レンダリングする必要がなくなるため、パフォーマンスの向上を期待できます。
React.memo()
React.memo()
はコンポーネントのレンダリング結果をメモ化するReact APIです。親コンポーネントから渡されたprops
について前回との変更差分をチェックし、コンポーネントが返した描画結果を記録してメモ化し、差分があった場合のみ再レンダリングを行います。
- 前回のpropsと新しく親コンポーネントから渡されたpropsを比較し、値が等価であるかチェックする
- 等価である場合、メモ化したコンポーネントを利用するため、再レンダリングをスキップする
- 等価でない場合、再レンダリングを行う
また、React.memo()
でコンポーネントをラップしても、ラップされたコンポーネント内でuseSate()
やuseContext()
を利用している場合、その変化に応じたコンポーネントの再レンダリングが行われます。頻繁に再レンダリングされる親コンポーネントを持つ子コンポーネント以外で利用しても大きなパフォーマンス効果を得ることはできません。
React.memo()を使用したサンプル
React.memo()を使用した成果物を作成しました。
再レンダリングが行われる度に、Consoleに変更したラジオボタンリストのメッセージが表示されます。
例えば、ラジオボタンリストAの値を変更した場合、Consoleに「ラジオボタンリストAが選択されました」と出力されます。この時、ラジオボタンリストBの値は変更されていないため、Consoleには出力されません。
コード解説
index.js
2回レンダリングされるのを防ぐためにStrictModeは削除しました。
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App />);
App.js
import React, { useState } from "react";
import "./styles.css";
// ラジオボタン用の要素を持った配列を設定
const items = [
{ id: 1, item: "東京" },
{ id: 2, item: "大阪" },
{ id: 3, item: "愛知" },
{ id: 4, item: "福岡" },
{ id: 5, item: "北海道" },
{ id: 6, item: "宮城" }
];
// 親コンポーネントRadioからprops(name, handleChange, value)を受け取る
const RadioOption = (props) =>
// map()を用いて配列itemsの要素を1つひとつ設定する
items.map((item) => {
return (
// ラジオボタン内で一意となるkey属性を設定
<label key={item.id}>
<input
type="radio"
value={item.item}
// ラジオボタンリストA・Bと2つ存在するため、リストを分けるのにnameを設定する
name={props.name}
// ラジオボタンで選択された値を親コンポーネントで定義されている状態変数valueA,valueBに渡す
onChange={props.handleChange}
// ラジオボタンの初期値(最初にチェックされている値)を設定する
// 親コンポーネントから渡されるvalueには初期値にitems[0].item=東京が設定されている
// そのため、最初にラジオボタンにチェックされているのは東京になる
checked={item.item === props.value}
/>
{item.item}
</label>
);
});
// 親コンポーネントRadioからprops(text, value)が渡される
// React.memoでRadioResultコンポーネントをラップする
// 親コンポーネントRadioから渡されたprops(text, value)を記録する
// 2回目以降は前回のprops(text, value)と比較し、等価でない場合のみ再レンダリングを行う
const RadioResult = React.memo((props) => {
console.log(`${props.text}が選択されました`);
return (
<p>
選択された値:<b>{props.value}</b>
</p>
);
});
const Radio = () => {
// ラジオボタンリストAで現在選択されている値を設定する状態変数valueA
// valueAを更新するsetValueA
const [valueA, setValueA] = useState(items[0].item);
// ラジオボタンリストBで現在選択されている値を設定する状態変数valueB
// valueBを更新するsetValueB
const [valueB, setValueB] = useState(items[0].item);
// ラジオボタンリストAで選択された値(value)を設定する関数handleChangeAを定義
const handleChangeA = (e) => setValueA(e.target.value);
// ラジオボタンリストBで選択された値(value)を設定する関数handleChangeBを定義
const handleChangeB = (e) => setValueB(e.target.value);
return (
<>
<div className="radio-list">
<div>
ラジオボタンリストA
<RadioOption
name="radioA"
value={valueA}
handleChange={handleChangeA}
/>
<RadioResult text="ラジオボタンリストA" value={valueA} />
</div>
<div>
ラジオボタンリストB
<RadioOption
name="radioB"
value={valueB}
handleChange={handleChangeB}
/>
<RadioResult text="ラジオボタンリストB" value={valueB} />
</div>
</div>
</>
);
};
export default function App() {
return <Radio />;
}
今回の処理は親コンポーネントであるRadio
からReact.memo()でラップされた子コンポーネントRadioResult
へprops(text, value)
を渡す。
text
は固定の文字列のため、変更される値はvalue
のみである。
今回のプログラムの構造は以下のようになっていますが、Radio
コンポーネントからRadioResult
コンポーネントへ渡されるvalueA
の値が変更されても、もう1つのRadioResult
コンポーネントへ渡されるvalueB
の値は変更されないため、ボタンリストAのみ再レンダリングされ、ンソール画面には「ボタンリストAが変更されました」と出力されます。
しかし、React.memo()を外した場合、親コンポーネントから渡されるvalue
が変更されると、例えボタンリストAのみ変更しても、ボタンリストBを再レンダリングされ、コンソール画面には「ボタンリストAが変更されました」、「ボタンリストBが変更されました」と両方出力されます。
styles.css
label {
display: block;
}
.radio-list {
display: flex;
justify-content: space-around;
}