レンダリングの最適化にmemoとuseCallbackとReact.useMemoを使う
Reactの開発は、再レンダリングを意識しながら開発をすることで、パフォーマンスの改善につなげることができます。
再レンダリングを制御するためにメモ化を行います。メモ化する技術のReact.memoとuseCallbackとReact.useMemoをまとめていきます。
メモ化の種類
Reactはメモ化することで、再レンダリングを制御・最適化することができます。
メモ化は前回の処理結果を保持しておくことで、処理を高速化することができます。
種類 | 対象 |
---|---|
React.memo | コンポーネント |
useCallback | コールバック関数 |
React.useMemo | 変数 |
再レンダリングが起きる条件
- Stateが更新されたコンポーネント
- Propsが変更されたコンポーネント
- 再レンダリングされたコンポーネント配下のすべてのコンポーネント
### 再レンダリングを起こしてみる
import ReactDom from "react-dom";
import { App } from "./App";
ReactDom.render(<App />, document.getElementById("root"));
);
import { useState, useCallback } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";
export const App = () => {
console.log("App レンダリング ");
const [num, setNum] = useState(0);
const onClickButton = () => {
setNum(num + 1);
};
const onClickReset = useCallback(() => {
setNum(0);
}, []);
return (
<>
<button onClick={onClickButton}>ボタン</button>
<p>{num}</p>
<Child1 onClickReset={onClickReset} />
<Child4 />
</>
);
};
import { Child2 } from "./Child2";
import { Child3 } from "./Child3";
const style = {
backgroundColor: "#AAFFFF ",
};
export const Child1 = (props) => {
console.log("Child1 レンダリング ");
const { onClickReset } = props;
return (
<div style={style}>
<p>Child1</p>
<button onClick={onClickReset}>リセット</button>
<Child2 />
<Child3 />
</div>
);
};
const style = {
backgroundColor: "#FFCCFF",
};
export const Child2 = () => {
console.log("Child2 レンダリング ");
return (
<div style={style}>
<p>Child2</p>
</div>
);
};
const style = {
backgroundColor: "#FFCCFF",
};
export const Child3 = () => {
console.log("Child3 レンダリング ");
return (
<div style={style}>
<p>Child3</p>
</div>
);
};
const style = {
backgroundColor: "#66FF00",
};
export const Child4 = () => {
console.log("Child4 レンダリング ");
return (
<div style={style}>
<p>Child4</p>
</div>
);
};
上記のコードで実行した後、カウントアップのボタンを押すと下記のようにすべてのコンポーネントが再レンダリングされていることが確認できます。
コンポーネントのメモ化にReact.memoを使う
React.memoは、コンポーネントをメモ化します。上記のコードのコンポーネントをReact.memoを使うように変更します。
const Component = memo(() =>{});
import { useState, memo, useCallback } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";
export const App = memo(() => { // memoに変更した
console.log("App レンダリング ");
const [num, setNum] = useState(0);
const onClickButton = () => {
setNum(num + 1);
};
const onClickReset = useCallback(() => {
setNum(0);
}, []);
return (
<>
<button onClick={onClickButton}>ボタン</button>
<p>{num}</p>
<Child1 onClickReset={onClickReset} />
<Child4 />
</>
);
});
import { memo } from "react";
import { Child2 } from "./Child2";
import { Child3 } from "./Child3";
const style = {
backgroundColor: "#AAFFFF ",
};
export const Child1 = memo((props) => { // memoに変更した
console.log("Child1 レンダリング ");
const { onClickReset } = props;
return (
<div style={style}>
<p>Child1</p>
<button onClick={onClickReset}>リセット</button>
<Child2 />
<Child3 />
</div>
);
});
import { memo } from "react";
const style = {
backgroundColor: "#FFCCFF",
};
export const Child2 = memo(() => { // memoに変更した
console.log("Child2 レンダリング ");
return (
<div style={style}>
<p>Child2</p>
</div>
);
});
import { memo } from "react";
const style = {
backgroundColor: "#FFCCFF",
};
export const Child3 = memo(() => { // memoに変更した
console.log("Child3 レンダリング ");
return (
<div style={style}>
<p>Child3</p>
</div>
);
});
import { memo } from "react";
const style = {
backgroundColor: "#66FF00",
};
export const Child4 = memo(() => {
console.log("Child4 レンダリング ");
return (
<div style={style}>
<p>Child4</p>
</div>
);
});
メモ化したことで、Appコンポーネントのみ再レンダリングされていることが確認することができました。
関数による再レンダリングを起こしてみる
カウンアップをリセットするボタンの関数を作成し、関数をChild1に渡すコードを作成しました。実行し、カウントアップを行うと先程メモ化した、Chile1の再レンダリングが発生してしまいます。
import { useState, memo } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";
export const App = memo(() => {
console.log("App レンダリング ");
const [num, setNum] = useState(0);
const onClickButton = () => {
setNum(num + 1);
};
// 追加しました
const onClickReset = () => {
setNum(0);
};
return (
<>
<button onClick={onClickButton}>ボタン</button>
<p>{num}</p>
<Child1 onClickReset={onClickReset} /> // 関数の受け渡しを追加しました
<Child4 />
</>
);
});
import { memo } from "react";
import { Child2 } from "./Child2";
import { Child3 } from "./Child3";
const style = {
backgroundColor: "#AAFFFF ",
};
export const Child1 = memo((props) => {
console.log("Child1 レンダリング ");
const { onClickReset } = props;
return (
<div style={style}>
<p>Child1</p>
<button onClick={onClickReset}>リセット</button>
<Child2 />
<Child3 />
</div>
);
});
関数のメモ化にuseCallbackを使ってみる
Reactの関数のメモ化は、useCallbackを使います。onClickReset関数を下記のようにuseCallbackで書きます。第1引数に関数を書き、第2引数には依存配列の要素を書きます。[ ]の場合は、レンダリングされた最初の1回のみ読み込まれます。2回目からは最初に作成されたものが使われます。
import { useState, memo, useCallback } from "react";
import { Child1 } from "./components/Child1";
import { Child4 } from "./components/Child4";
export const App = memo(() => {
console.log("App レンダリング ");
const [num, setNum] = useState(0);
const onClickButton = () => {
setNum(num + 1);
};
const onClickReset = useCallback(() => { //useCallbackを使うように変更した
setNum(0);
}, []);
return (
<>
<button onClick={onClickButton}>ボタン</button>
<p>{num}</p>
<Child1 onClickReset={onClickReset} />
<Child4 />
</>
);
});
Child1が、再レンダリングされなくなりました。
変数のメモ化にReact.useMemoを使う
変数や値のメモ化には、React.useMemoを使います。第1引数に計算式、第2引数に依存配列の要素を書きます。依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します。下記のコードの場合、aとbの値が変わらない場合、最初にレンダリングした時の値が使い回されます。
const memoizedValue = useMemo(() => {
return a + b;
}, [a, b]);