Reactはアプリが大きくなるほど、再レンダリングのタイミングが複雑になります。
なので、思わぬところでパフォーマンスが下がってしまいます。
つまり、Reactを使っているエンジニアはきちんと、この再レンダリングの最適化を学ぶ必要があるのです。
「Reactを学んだけれど、再レンダリングって何?最適化ってどうやるの?」
という人にはこの記事が役に立つかと思います。
読み終わる頃には、
「あー、再レンダリングの最適化って意外と簡単なのね」
という風になっているかと思います。
本記事の内容
そもそも再レンダリングっていつ起こるの?
再レンダリングを最適化するための3つの方法
それでは、初めていきます。
よりわかりやすいように、本記事は実践形式となっていますので、ぜひ一緒に進めていきましょう。
Reactエンジニアを目指している方へ、
知識0からReactエンジニアになるまでのロードマップをまとめました。
こちらも記事もぜひ参考にどうぞ。
スクールでReactを深く学びたい方へ、
Reactを学べるおすすめのプログラミングスクールもまとめてますので、こちらもどうぞ。
Reactのおすすめ教材が知りたい方へ、
現役のReactエンジニアが>Reactのおすすめ教材をまとめました。
こちらも記事もぜひ参考にどうぞ。
\月1,980円から講師に質問し放題のプログラミングスクール/
そもそも再レンダリングはいつ起こるのか?
まず、Reactにおいて再レンダリングがいつ起こるのか?
ということですが、それは3つあります。
- stateが更新された時
- propsが更新された時
- 親コンポーネントが再レンダリングされた時
それぞれ詳しく説明していきます。
1、stateが更新された時
const CountUp = () => {
const [count, setCount] = useState(0);
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
</>
)
}
上記のコードではボタンが押された時に、「count」という「state」が更新されるようになってます。
このように、「state」が更新される時に再レンダリングが起こります。
これが、「state」が更新されたときの例です。
2、propsが更新された時
次に、「props」が更新された時です。
こちらは、単体で例を出すのが難しいので、後で詳しく説明します。
とりあえず今は、「props」が更新された時も再レンダリングが起こる。
ということを覚えておけばOKです。
3、親コンポーネントが再レンダリングされた時
最後は、親コンポーネントが再レンダリングされた時です。
例えば、1のコードに子コンポーネントを追加します。
const Child = () => {
console.log("child is called");
return(
<h3>child</h3>
);
}
export const CountUp = () => {
const [count, setCount] = useState(0);
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
<Child />
</>
)
}
親コンポーネントの「state」を変更するような処理を行った場合は、親コンポーネントだけでなく、子コンポーネントも再レンダリングされます。
ボタンを押す度に「child is called」とログに表示されているかと思います。
これが毎回レンダリングが走っている証明になります。
再レンダリングを最適化するための3つの方法
さて、再レンダリングが起こる3つのタイミングを見てきました。
では実際に、
「どうやればこの再レンダリングを最適化できるのか?」
ということを実践を通して学んでいきましょう。
再レンダリングを最適化するための方法は主に3つあります。
- memo
- useCallback
- useMemo
1つずつ解説していきます。
0、環境構築
まずは、実践形式で学んでいくために環境構築をしましょう。
何も難しいことはなくcreate-react-appをすればOkです。
後は、「App.js」の一番外の「divタグ」以外を消します。
そして、src配下に「components/memo」というディレクトリを作りましょう。
そこに、「CountUp.jsx」というファイルを作り関数コンポーネントの雛形だけを書きエクスポートして、「App.js」側で表示させれば準備完了です。
1、memo
まず、こちらの機能を説明するために簡単なカウントアップの機能を実装していきます。
そして、その子のChildコンポーネントを書きます。
コードは以下の通り、
import {useState, memo} from "react";
const Child = () => {
console.log("child is called");
return(
<h3>child</h3>
);
}
export const CountUp = () => {
const [count, setCount] = useState(0);
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
<Child />
</>
)
}
この状態だと、カウントアップされる度にログに「child is called」と表示されるかと思います。
これは、CountUpコンポーネントの「state」である「count」が更新されレンダリングが起こり、その子であるChildコンポーネントも再レンダリングが起こるためです。
しかし、Childコンポーネントの中身は何1つ変わっていないので、実際再レンダリングは不要です。
そこで、役に立つのが「memo」という機能です。
使い方は簡単で、コンポーネントをを「memo」という文字で囲むだけでOKです。
これをすることで、「memo」で囲まれたコンポーネントは、例え親コンポーネントが更新されてもpropsが変更されない限りは再レンダリングされなくなります。
つまり、不要な再レンダリングを起こさなくなります。
memo化したchildコンポーネントは以下の通り。
import {useState, memo} from "react";
const Child = memo(() => {
console.log("child is called");
return(
<h3>child</h3>
);
});
ちなみに、memoを使うためにはきちんとインポートしておく必要がありますので忘れないように。
2、useCallback
次も、まずは解説の前に必要な機能を実装していきます。
「childCount」という「state」を定義します。
次に、「onClickChildCount」という「childCount」をカウントアップしていく関数を定義します。
そして、それをChildコンポーネントに渡します。
Childコンポーネント内でそれを使って、「childCount」をカウントアップしていくボタンを作成します。
これで、準備完了です。
import {useState, memo, useCallback} from "react";
const Child = memo((props) => {
console.log("child is called");
return(
<div>
<h3>child</h3>
<button onClick={props.onClick}>child count up!</button>
</div>
);
});
export const CountUp = () => {
const [count, setCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const onClickChildCount = () => {
setChildCount(childCount + 1);
};
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
<Child onClick={onClickChildCount}/>
<p>{childCount}</p>
</>
)
}
では、一番上のカウントアップボタンを押してください。
ログを見て何か気づきましたか?
そうです。
せっかく先ほどmemo化したChildコンポーネントがまた再レンダリングされるようになってしまっているのです。
なぜかと言うと、今回は「props」としてアロー関数で作った関数を渡しているのですが、このアロー関数で作った関数は毎回新しい関数が生成されていると判断されるからです。
なので、「props」が更新されている判断がされ、再レンダリングが起きているのです。
そして、それを解決するのが「useCallback」です。
これは、変数を引数として渡し、その変数の変更がない場合は再生成を行わないようにしてくれます。
import {useState, memo, useCallback} from "react";
const Child = memo((props) => {
console.log("child is called");
return(
<div>
<h3>child</h3>
<button onClick={props.onClick}>child count up!</button>
</div>
);
});
export const CountUp = () => {
const [count, setCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const onClickChildCount = useCallback(() => {
setChildCount(childCount + 1);
},[childCount]);
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
<Child onClick={onClickChildCount}/>
<p>{childCount}</p>
</>
)
}
これで、親コンポーネントが再レンダリングされても、再レンダリングが起きなくなっているかと思います。
3、useMemo
最後に紹介するのが、「useMemo」になります。
これは、変数をmemo化するための機能です。
まず、説明のために追加の機能を実装していきます。
「count」の数を2倍したものを「dubledCount」という変数に入れて、それを表示させるようにします。
コードは以下の通り。
import {useState, memo, useCallback, useMemo} from "react";
const Child = memo((props) => {
console.log("child is called");
return(
<div>
<h3>child</h3>
<button onClick={props.onClick}>child count up!</button>
</div>
);
});
export const CountUp = () => {
const [count, setCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const onClickChildCount = useCallback(() => {
setChildCount(childCount + 1);
},[childCount]);
const dubleCount = (count) => {
console.log("call dublecount")
return(count * 2);
}
const dubledCount = () => dubleCount(count);
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
<p>{dubledCount}</p>
<Child onClick={onClickChildCount}/>
<p>{childCount}</p>
</>
)
}
このままでは、「count」が変更されていない時でも毎回2倍の計算がされて無駄になってしまいます。
試しに、「childCount」の方のカウントアップボタンを押した時も、ログに「call dublecount」と表示されてしまっているかと思います。
今回の例ならまだ良いのですが、重い処理とかになると無駄にパフォーマンスが下がってしまいます。
なので、memo化して「count」が変更されていない時は2倍の計算がされないようにします。
memo化したコードは以下の通りです。
const Child = memo((props) => {
console.log("child is called");
return(
<div>
<h3>child</h3>
<button onClick={props.onClick}>child count up!</button>
</div>
);
});
export const CountUp = () => {
const [count, setCount] = useState(0);
const [childCount, setChildCount] = useState(0);
const onClickChildCount = useCallback(() => {
setChildCount(childCount + 1);
},[childCount]);
const dubleCount = (count) => {
console.log("call dublecount")
return(count * 2);
}
const dubledCount = useMemo(() => dubleCount(count), [count]);
return(
<>
<h2>count up</h2>
<button onClick={() => setCount(count + 1)}>count up!</button>
<p>{count}</p>
<p>{dubledCount}</p>
<Child onClick={onClickChildCount}/>
<p>{childCount}</p>
</>
)
}
これで毎回不要なレンダリングが起きなくなります。
Reactの再レンダリングの仕組みと最適化を解説 まとめ
本記事では、Reactの再レンダリングのタイミングとそれを最適化するための方法を学びました。
まとめます。
- stateが更新された時
- propsが更新された時
- 親コンポーネントが再レンダリングされた時
最適化するための方法
1、memo
Propsが更新されてない時は、親コンポーネントが再レンダリングされても、memo化したコンポーネントは再レンダリングされなくなる
2、useCallback
Propsにアロー関数を渡している時は、毎回新しい関数を再生成されているために起こる不要な再レンダリングを無くす
3、useMemo
変数をmemo化して、レンダリングの最適化をする
意外とここをわかってない人とかいるので、知ってるだけでかなり重宝される存在になれるかと思います。
今回学んだ知識は忘れてもすぐに見直せるように、githubなどに上げておくことをオススメします。
今回のコードはこちらにも載ってますので、参考にしてみてください。
https://github.com/hinoharashinya/react-commentary/tree/master/src/components/memo
僕のブログでも他にプログラミングについて解説している記事がありますので、興味のある方はぜひ見てください。
Reactエンジニアになりたい人はこちらからどうぞ
Reactを学べるおすすめのプログラミングスクールを知りたい方はこの記事をどうぞ。
Reactのおすすめ教材を知りたい人はこの記事をどうぞ。
「プログラミングスクールが高くて通えない。。」
といった悩みがこのサービスで解決します。
それは、次世代型のサブスクプログラミングスクールになります。
具体的に、このスクールは以下のことが可能です。
- 講師とのマンツーマンレッスン
- 質の高いかなりボリュームのある教材
- 講師に質問し放題
そして、お値段はたったの1,980円から。
これで高いお金を払わずに、エンジニアになることが可能です。
今なら、全額返金保証もあります。
エンジニアを目指す人も年々増えているで、お早めにどうぞ。
当サイト限定の、初月50%OFFクーポン(HINOSHIN)あり
>>侍テラコヤの評判・口コミ|現役エンジニアが実際に使ってみた感想