📽️

Reactの再レンダリングを最適化するための3つの方法

2023/10/08に公開

始めに

この記事ではまず、再レンダリングが起きる条件について触れてから
再レンダリングが最適化される前のサンプルプログラムを変更しながら最適化する方法を覚えていきましょう。

再レンダリングが起きる条件

まずは、再レンダリングの最適化を考える前に再レンダリングが起きる条件について確認しておきましょう。

再レンダリングが発生するタイミングは3つあります。

  1. Stateが更新されたコンポーネント
  2. Propsが変更されたコンポーネント
  3. 再レンダリングされたコンポーネント配下の子要素

サンプルコードについて

親コンポーネントには入力フォームと子コンポーネントの表示を切り替えるボタンを配置
子コンポーネントはレンダリングされる度に重い処理が実行される

親コンポーネントのコード

import { useState } from "react";
import styled from "styled-components";

import ChildComponent from "./ChildComponent";

const SButton = styled.button`
  padding: 10px 20px;
  background-color: #f4e13b;
  border: none;
  outline: none;
  transition: 0.3s;
  &:hover {
    opacity: 0.8;
  }
`;

const SInput = styled.input`
  width: 200px;
  padding: 10px;
  border: solid 1px #dfdfdf;
  border-radius: 5px;
  outline: solid 0px #dfdfdf;
  transition: 0.1s;
  &:focus {
    border: solid 1px skyblue;
    outline: solid 1px skyblue;
  }
`;

const App = () => {
  console.log("app");
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);
  const onChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };
  const onClickOpen = () => {
    setOpen((prev) => !prev);
    console.log(open);
  };
  return (
    <>
      <SInput type="text" value={text} onChange={(e) => onChangeText(e)} />
      <br />
      <br />
      <SButton onClick={onClickOpen}>表示</SButton>
      <ChildComponent open={open} />
    </>
  );
};

export default App;

子コンポーネントのコード

import styled from "styled-components";

type Props = {
  open: boolean;
};

const SChildContainer = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 200px;
  background-color: khaki;
`;

const ChildComponent = ({ open }: Props) => {
  console.log("子コンポーネントがレンダリングされました。");
  
  // [0, 1, 2, 3, 4, ...1999] 配列を作成
  const data = [...Array(2000).keys()];

  // 2000件のデータをコンソールに出力
 data.forEach((number) => {
    console.log(number);
  })

  return (
    <>
      {open && (
        <SChildContainer>
          <p>子コンポーネント</p>
        </SChildContainer>
      )}
    </>
  );
};

export default ChildComponent;

このコードでは入力フォームに文字を入力する度にStateが更新されるため、親コンポーネントで再レンダリングが発生します。

再レンダリングされる3つ条件の内

  • 再レンダリングされたコンポーネント配下の子要素

この条件に当てはまるため、子コンポーネントも再レンダリングされます。なのでこのままでは文字を入力する度に重たい処理が走るため、とても重いアプリになってしまいます。
それを解決するのが次の方法です。

最適化の方法1つ目 memo

Propsに変更がない場合は再レンダリングされないようにしたい。そんな時に使うのがmemoです。
使い方は簡単コンポーネント自体をすべてmemoで囲むだけです。

import { memo } from "react";

const ChildComponent = memo(({ open }: Props) => {
// 省略
});

memo化すること自体にもコストがあるためすべてのコンポーネントをmemo化する必要なないと思いますが、複数の要素だったり、今後肥大化していくようなコンポーネントでは積極的に使っていきましょう。

最適化の方法2つ目 useCallback()

上記のコードに子コンポーネントを閉じるためのコードを追加します。

// 親コンポーネント
const App = () => {
  // 省略
  const onClickClose = () => {
    setOpen(false);
  }
  return (
    <>
      { // 省略 }
      <ChildComponent open={open} onClickClose={onClickClose} />
    </>
  );
};

export default App;
// 子コンポーネント
const ChildComponent = memo(({ open, onClickClose }: Props) => {
// 省略
  return (
    <>
      {open && (
        <SChildContainer>
          <p>子コンポーネント</p>
          <SButton onClick={onClickClose}>閉じる</SButton>
        </SChildContainer>
      )}
    </>
  );
});

export default ChildComponent;

子コンポーネントに関数を1つ渡しただけなんですけど、
この状態でもう一度入力フォームに文字を入力する度に先ほどmemo化したはずなのにまた再レンダリングが走ってしまいます。
Propsとしてアロー関数を渡すと、親コンポーネントが再レンダリングされる度に新しく別の関数が生成されたと判断されてしまい、子コンポーネントまで再レンダリングされてしまいます。
渡す関数の処理が変わらない場合はuseCallback()で囲むことで解決します。
useCallbackの第2引数には依存配列を設定することができ、依存配列に変更があった場合に関数が再生成されます。

const onClickClose = useCallback(() => {
  setOpen(false);
}, []);

最適化の方法3つ目(おまけ) useMemo()

変数自体をmemo化する時に使うのがuseMemo()です。

const temp = useMemo(() => {
  return 1 + 3;
}, []);
console.log(temp);

変数に設定する中の処理が複雑になっている場合などに使うと良い。

まとめ

再レンダリングが発生するタイミングは3つ

  • Stateが更新されたコンポーネント

  • Propsが変更されたコンポーネント

  • 再レンダリングされたコンポーネント配下の子要素

  • Propsに変更がない場合は再レンダリングされないようにするのがmemoです。
    使い方はコンポーネントをmemoで囲むだけ

const Conponent = memo(() => {
  // 省略
});
  • コンポーネントが再レンダリングされるたびに新しい関数を作成するのを防ぐのがuseCallback()です。
const func = useCallback(() => {
  // 省略
}, []);
  • 変数自体をmemo化する時に使うのがuseMemo()です。
const temp = useMemo(() => {
  // 省略
}, []);

最後に、Reactのレンダリング最適化は、パフォーマンス向上に重要な要素です。コンポーネントの再レンダリングを最小限に抑えることで、アプリケーションの動作をスムーズにし、ユーザーエクスペリエンスを向上させることができます。本記事で紹介した最適化の手法を実践することで、より効果的なReactアプリケーションを開発することができるでしょう。是非、実際のプロジェクトに取り組んでみてください。もし何か質問や疑問があれば、お気軽にお問い合わせください。皆さんのReact開発のお手伝いができれば幸いです。

Discussion