再レンダリングと一緒に理解するReact.memo
こんにちは👋
今回はReact.memo
を再レンダリングと絡めて使い方と使い所なんかをイメージしやすく簡単に説明していきたいと思います。
再レンダリングが行われるシーン
再レンダリングが行われる条件としては主に以下の3つがあります。
- stateが更新されたとき
- propsが変更されたとき
- 親コンポーネントが再レンダリングされたとき(親コンポーネントが再レンダリングされたとき、全ての子コンポーネントが再レンダリングされる)
Reactで規模の大きいアプリを作成する際、上記の条件を意識しないと余計なコンポーネントまで再レンダリングされてしまい、画面がもっさりしてしまう原因となってしまいます。
メモ化とは?
Reactには前述のような無駄な計算を行わないようにするためにメモ化と呼ばれる概念があります。
メモ化は、何らかの計算によって得られた値を記憶しておき、その値が再度必要になったときに再計算を行うことなく記憶しておいた値を再利用するといったものになります。
React.memo
とは?
React.memo
は、あるコンポーネントに新しく渡されたprops
が前回渡されたprops
と同じであった場合、再レンダリングを行わないようにするための機能です。
それでは、実際に使用してその効果を確認してみましょう。
React.memo
を体験する
React.memo
を使用しない場合
・まずはReact.memo
を使用していない場合の再レンダリングについて確認したいので、以下のようなサンプルを作成します。
import { useState } from "react";
import styled from "styled-components";
// Component style
const StParentContainer = styled.div`
height: 400px;
width: 500px;
background-color: #ffb6b9;
margin: 0 auto;
`;
const StChildContainer = styled(StParentContainer)`
height: 150px;
width: 300px;
background-color: #8ac6d1;
`;
const StAreaName = styled.p`
font-size: 2rem;
text-align: center;
color: white;
`;
const StCounterContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 10px;
`;
// Components
const ChildArea = ({ isDisplay }) => {
console.log("Child Areaがレンダリングされました");
return isDisplay ? (
<StChildContainer>
<StAreaName>Child Area</StAreaName>
</StChildContainer>
) : null;
};
export const App = () => {
console.log("Parent Areaがレンダリングされました");
const [count, setCount] = useState(0);
const [isDisplay, setIsDisplay] = useState(false);
const onClickCountUp = () => {
setCount(count + 1);
if (count % 5 === 4) {
setIsDisplay(!isDisplay);
}
};
return (
<StParentContainer>
<StAreaName>Parent Area</StAreaName>
<StCounterContainer>
<p>{count}</p>
<button onClick={onClickCountUp}>Count Up</button>
</StCounterContainer>
<ChildArea isDisplay={isDisplay} />
</StParentContainer>
);
};
下のCodeSandboxと合わせて説明しますが、ピンク色のエリアを親コンポーネント(Parent Area
)、水色のエリアを子コンポーネント(Child Area
)として、Count Up
ボタンが1クリックされるごとにsetCount()
によってcount
が更新され、Parent Area
に描画されている数字が1ずつ増えていきます。次に、count
が5で割り切れる数字になることを条件として、setIsDisplay()
によってisDisplay
の真理値が反転し、isDisplay
が子コンポーネントであるChildArea
に渡され、isDisplay
の値によってChildArea
の表示、非表示が切り替わるようになっています。そして最後に、レンダリングが行われた事が分かるようにconsole.log()
でコンソールにメッセージを表示するようにしました。
それでは、実際にCount Up
ボタンをクリックしてConsoleに表示されるメッセージを確認してみましょう。
いかがだったでしょうか。ボタンを押すたびにParent Area
とChild Area
の両方が再レンダリングされていることが確認できたかと思います。
ここでよく考えてみると、isDisplay
に変化がない場合はChildArea
に変化がないので、isDisplay
の値が変わる場合以外はボタンが押されるたびにChildArea
について無駄な再計算が行われていることになります。
そこで、ChildArea
が受け取っているpropsの値に変化があった場合のみ再レンダリングが行われるように、React.memo
を使用してパフォーマンスの改善をしていきたいと思います。
React.memo
を使用したパフォーマンス改善
・React.memo
の使い方はとても簡単で、メモ化したいコンポーネントをReact.memo
でラップしてあげるだけです。
今回の場合はChildArea
に対して無駄な再計算が行われることを回避したいので、ChildArea
をReact.memo
でラップします。
import { memo, useState } from "react"; // ① memoのインポートを追加
import styled from "styled-components";
// Component style
const StParentContainer = styled.div`
height: 400px;
width: 500px;
background-color: #ffb6b9;
margin: 0 auto;
`;
const StChildContainer = styled(StParentContainer)`
height: 150px;
width: 300px;
background-color: #8ac6d1;
`;
const StAreaName = styled.p`
font-size: 2rem;
text-align: center;
color: white;
`;
const StCounterContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 10px;
`;
// Components
// ② ChildAreaをmemoでラップしてあげる
const ChildArea = memo(({ isDisplay }) => {
console.log("Child Areaがレンダリングされました");
return isDisplay ? (
<StChildContainer>
<StAreaName>Child Area</StAreaName>
</StChildContainer>
) : null;
});
export const App = () => {
console.log("Parent Areaがレンダリングされました");
const [count, setCount] = useState(0);
const [isDisplay, setIsDisplay] = useState(true);
const onClickCountUp = () => {
setCount(count + 1);
if (count % 5 === 4) {
setIsDisplay(!isDisplay);
}
};
return (
<StParentContainer>
<StAreaName>Parent Area</StAreaName>
<StCounterContainer>
<p>{count}</p>
<button onClick={onClickCountUp}>Count Up</button>
</StCounterContainer>
<ChildArea isDisplay={isDisplay} />
</StParentContainer>
);
};
コメントアウトで①、②としている部分がReact.memo
の実装部分になります。
それでは、CodeSandboxでどんな変化があったか確認してみましょう。
ボタンを押してもisDisplay
の値に変更がない場合はChildArea
の再計算が行われず、isDisplay
の値に変化があった場合のみChild Areaがレンダリングされました
とコンソールに表示されると思います。
最後に
今回はReact.memo
を使用することで、コンポーネントが受け取ったpropsの値に変化がないときに無駄な再計算を行わないようにできることを確認しました。
こうなると色んなコンポーネントに対してReact.memo
を使用したくなりますが、メモ化にもコストはかかっています。なので、パフォーマンスチューニングの際にはメモ化のコストとコンポーネントの再計算のコストをしっかり比較して実装を行うようにすると良いと思います。
以上で再レンダリングとReact.memo
についての説明を終わります。
最後までお読みいただきありがとうございました。
Discussion