プログラミング

【React】再レンダリングの仕組みと最適化を紹介【意外と簡単】

Reactの再レンダリングの仕組みと最適化

Reactはアプリが大きくなるほど、再レンダリングのタイミングが複雑になります。
なので、思わぬところでパフォーマンスが下がってしまいます。

つまり、Reactを使っているエンジニアはきちんと、この再レンダリングの最適化を学ぶ必要があるのです。

「Reactを学んだけれど、再レンダリングって何?最適化ってどうやるの?」
という人にはこの記事が役に立つかと思います。

読み終わる頃には、
「あー、再レンダリングの最適化って意外と簡単なのね」
という風になっているかと思います。

本記事の内容
そもそも再レンダリングっていつ起こるの?
再レンダリングを最適化するための3つの方法

それでは、初めていきます。
よりわかりやすいように、本記事は実践形式となっていますので、ぜひ一緒に進めていきましょう。

Reactエンジニアを目指している方へ、

知識0からReactエンジニアになるまでのロードマップをまとめました。
こちらも記事もぜひ参考にどうぞ。

Reactロードマップ
【完全無料】Reactエンジニアへなるための学習ロードマップ【高年収】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

スクールでReactを深く学びたい方へ、

Reactを学べるおすすめのプログラミングスクールもまとめてますので、こちらもどうぞ。

reactを学べるプログラミングスクール
【最新】Reactを学べるおすすめプログラミングスクール3選Reactを学びたいですか?プログラミング学習で最も効率的なのはプログラミングスクールに通うことです。なので今回は、おすすめのプログラミングスクールを厳選しました。これでスクール選びで失敗することは無いです。Reactは需要が高いスキルなので、今すぐ学びましょう。...

Reactのおすすめ教材が知りたい方へ、

現役のReactエンジニアが>Reactのおすすめ教材をまとめました。
こちらも記事もぜひ参考にどうぞ。

【入門】React初心者が0から知識を身につける勉強法・教材のまとめ「React初心者だけど、勉強して入門したい」と思っている方必見。記事では、React初心者に向けて入門レベルからおすすめの学習教材を紹介しています。Reactエンジニアである僕が、勉強法なども合わせて解説しているのかなり参考になるかと。...

\月1,980円から講師に質問し放題のプログラミングスクール/

そもそも再レンダリングはいつ起こるのか?

まず、Reactにおいて再レンダリングがいつ起こるのか?
ということですが、それは3つあります。

  1. stateが更新された時
  2. propsが更新された時
  3. 親コンポーネントが再レンダリングされた時

それぞれ詳しく説明していきます。

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つあります。

  1. memo
  2. useCallback
  3. 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エンジニアへなるための学習ロードマップ【高年収】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

Reactを学べるおすすめのプログラミングスクールを知りたい方はこの記事をどうぞ。

reactを学べるプログラミングスクール
【最新】Reactを学べるおすすめプログラミングスクール3選Reactを学びたいですか?プログラミング学習で最も効率的なのはプログラミングスクールに通うことです。なので今回は、おすすめのプログラミングスクールを厳選しました。これでスクール選びで失敗することは無いです。Reactは需要が高いスキルなので、今すぐ学びましょう。...

Reactのおすすめ教材を知りたい人はこの記事をどうぞ。

【入門】React初心者が0から知識を身につける勉強法・教材のまとめ「React初心者だけど、勉強して入門したい」と思っている方必見。記事では、React初心者に向けて入門レベルからおすすめの学習教材を紹介しています。Reactエンジニアである僕が、勉強法なども合わせて解説しているのかなり参考になるかと。...
ABOUT ME
ひのしん
【執筆者情報】ひのしんです。「正しい努力をすれば誰でも成果は出せる」 をモットーに発信しています。元ニートのフリーランスエンジニア。 年収240万→840万,TOEIC425→885。0スキルアップに関する知識を発信してます。分かりやすさ、効率の良さ、簡単さを意識して記事を書いています。
【次世代型】サブスクのプログラミングスクール

「プログラミングスクールが高くて通えない。。」

といった悩みがこのサービスで解決します。

それは、次世代型のサブスクプログラミングスクールになります。

具体的に、このスクールは以下のことが可能です。

  • 講師とのマンツーマンレッスン
  • 質の高いかなりボリュームのある教材
  • 講師に質問し放題

そして、お値段はたったの1,980円から。

これで高いお金を払わずに、エンジニアになることが可能です。

今なら、全額返金保証もあります。

エンジニアを目指す人も年々増えているで、お早めにどうぞ。

当サイト限定の、初月50%OFFクーポン(HINOSHIN)あり

>>侍テラコヤの評判・口コミ|現役エンジニアが実際に使ってみた感想

\全額返金保証/
侍テラコヤを詳しく見る