【React】レンダリングの最適化

# この記事の目的
- Reactの理解とアウトプット、振り返り用
- Reactでよく使われている基本的な技術を言語化できるようする


# Reactのレンダリングについて
レンダリングとは、[React Docs BETA](https://beta.reactjs.org/learn/render-and-commit)では下記のように説明されています。(DeepL翻訳を使用済み)

> 「レンダリング」とは、React がコンポーネントを呼び出すことです。
> - 最初のレンダリングでは、React はルート コンポーネントを呼び出します。
> - それ以降のレンダリングでは、React はレンダリングのトリガーとなった状態更新の関数コンポーネントを呼び出します。

簡単にいうと、Reactが関数コンポーネントを呼び出すことを**レンダリング**と呼んでいます。
コンポーネントについては[別記事](https://qiita.com/pikimaru/items/f19ee1d41194cbf0e90e)に記載してますのでご参照ください。

# レンダリングのタイミング
- 初回レンダリング
- コンポーネントの状態が更新時のレンダリング

> 更新されたコンポーネントが子コンポーネントを持っている場合、Reactはその子コンポーネントを次にレンダリングしていく。この処理は>>再起的に行われます。

簡単に言うと親子コンポーネントの状態が更新されると付随する子コンポーネントすべてがレンダリングされます
こうなると親コンポーネントの状態は更新したいけど、子コンポーネントではその状態を使用しない（依存しない）ためレンダリングを行いたくない場合などが出てきます。（無駄なレンダリングを回避したい）
そのような場合に今回レンダリングの最適化についてこれから記載していきます。

# レンダリングの最適化
Reactのレンダリングの最適化をする下記の3つを紹介します
- memo
- useCallBack[
- useMemo

# メモ化とは
ここからメモ化というワードが出てくるので、メモ化についてまず説明します。
メモ化とは同じ結果を返す処理について、初回のみ処理を実行して記録しておき、値が必要となった2回目以降は、前回の処理結果を計算することなく呼び出し値を得られるようにすることです。
呼び出されるたびに計算しなくて良くなることからパフォーマンス向上が期待できるものになります。

# memo
React.memo とは、コンポーネントをメモ化する関数。
React.memoは親から子コンポーネントに渡している props が更新されない限り子コンポーネントは再レンダリングされなくなる。
```./ChildArea.jsx

import { memo } from "react";

const style = {
  width: "100%",
  height: "200px",
  backgroundColor: "khaki"
};
// memo()でコンポーネント自体（アロー関数）を囲うことで、propsが変更されない限り再レンダリングさせないことができる
export const ChildArea = memo((props) => {
  const { open, onClickClose } = props;
  const data = [...Array(2000).keys()];
  // 2000件の配列をループで回してコンソールログを出力する（ChildAreaコンポーネントのレンダリングの度に走る重い処理）
  data.forEach(() => {
    console.log("...");
  });
  console.log("ChildAreaがレンダリングされた！");

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子コンポーネント</p>
          <button onClick={onClickClose}>閉じる</button>
        </div>
      ) : null}
    </>
  );
});
```

# useCallBack
useCallBackは、メモ化されたコールバック関数を返す。
関数の実行時に前回実行された関数と入力された引数を比較し、変更がなければ前回の実行によって得られた値をそのまま返す。
```./ChildArea.jsx
import { useState, useCallback, useMemo } from "react";
import { ChildArea } from "../ChildArea";
import "./styles.css";

export default function App() {
  console.log("App");
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);
  const onChangeText = (e) => setText(e.target.value);
  const onClickOpen = () => setOpen(!open);

  /** 関数の処理が変わらない場合は、同じものを使い回させたいのでuseCallBackで囲ってあげる
   *  useCallBackは第一引数に関数、第二引数に依存配列を受け取る
   *  第二引数に空配列を指定すると、初回レンダリングに生成された関数を使い回す
   *  第二引数に変数を指定すると、その変数に変更があった場合のみ関数を再生成する
   */
  const onClickClose = useCallback(() => setOpen(false), [setOpen]);
  const temp = useMemo(() => 1 + 3, []);
  console.log(temp);
// 子コンポーネントのPropsにアロー関数を渡す時、子コンポーネント側で毎回新しい関数を生成していると判断される
//関数を普通に渡すだけだと、子コンポーネントをmemo化していてもPropsが変更されたと判断されるので
//子コンポーネントの再レンダリングが走ってしまう
  return (
    <div className="App">
      <input value={text} onChange={onChangeText} />
      <br />
      <br />
      <button onClick={onClickOpen}>表示</button>
      <ChildArea open={open} onClickClose={onClickClose} />
    </div>
  );
}
```

# useMemo
useMemoは関数の結果を保持するためのフック。
何回やっても結果が同じ場合の値などを保存(メモ化)し、そこから値を再取得する。
不要な再計算をスキップすることから、パフォーマンスの向上が期待出来る。
useCallbackとの違いはuseCallbackは関数自体をメモ化するが、useMemoは関数の結果を保持する。
```./ChildArea.jsx
import { useState, useCallback, useMemo } from "react";
import { ChildArea } from "../ChildArea";

import "./styles.css";

export default function App() {
  console.log("App");
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);
  const onChangeText = (e) => setText(e.target.value);
  const onClickOpen = () => setOpen(!open);
  const onClickClose = useCallback(() => setOpen(false), [setOpen]);
  /** 変数自体のメモ化をuseMemoで行うことができる
   *  useMemoは第一引数に関数、第二引数に依存配列を受け取る
   *  第二引数に空配列を指定すると、初回レンダリングに生成された変数を使い回す
   *  第二引数に変数を指定すると、その変数に変更があった場合のみ変数を再生成する
   */
  const temp = useMemo(() => 1 + 3, []);
  console.log(temp);

  return (
    <div className="App">
      <input value={text} onChange={onChangeText} />
      <br />
      <br />
      <button onClick={onClickOpen}>表示</button>
      <ChildArea open={open} onClickClose={onClickClose} />
    </div>
  );
}
```

# まとめ
- 基本的に、複数の要素から成り立っているコンポーネントや、今後肥大化が予想されるコンポーネントはmemoで囲ってあげる。
- 関数の処理が変わらない場合は、同じものを使い回させたいので関数全体をuseCallBackで囲ってあげる。
- Propsに関数を受け取るコンポーネントの再レンダリングを最適化する場合は、コンポーネントのmemo化と関数定義にuseCallBackを設定することをセットとする。
- 変数自体のメモ化をuseMemoで行うことができる。
- そこまで使う機会はないが、変数に設定する中の処理が複雑になっている時などのケースでは有効である。