[React]再レンダリングの仕組みと最適化について

再レンダリング最適化について勉強した時のメモです。

# 再レンダリングが起きる条件
- stateが更新されたとき。
- propsが変更されたとき
- 親コンポーネントが再レンダリングされた時のコンポーネント配下の子要素。

以下の場合Bが再レンダリングされるとCも再レンダリングされる。
![スクリーンショット 2021-10-12 11.16.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/343b3d9c-e3d5-8cbd-a3cb-e4a7dcccefe5.png)


## Stateが更新された時
```react
import { useState } from "react";

export const App = () => {
  console.log("Appレンダリング")

  const [text, setText] = useState("");

  const onChangeText = (e) => setText(e.target.value);

  return (
    <div className="App">
      <input value={text} onChange={onChangeText} />
    </div>
  );
};
```
![①.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/3060bb25-4454-cdfb-cf8f-d960444936ff.gif)

この場合inputのテキストボックスに値を入れるたびにconsoleが走るのでstateが変わるたびに再レンダリングが発生している。

## propsが更新された時
```react:src/App.jsx
import { useState } from "react";
import { ChildArea } from "./components/ChildArea";

export const App = () => {
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);

  const onChangeText = (e) => setText(e.target.value);

  const onClickOpen = () => setOpen(!open);

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

```react:src/components/ChildArea.jsx
export const ChildArea = (props) => {
  const { open } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
};
```
![②.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/8a4bf4d1-8d49-7a02-d02f-1db54522c8b5.gif)
`ChildArea`に`open`というpropsを渡しているのでボタンを押すたびに再レンダリングが起きている。

## 親コンポーネントが再レンダリングされた時
```react:src/App.jsx
import { useState } from "react";
import { ChildArea } from "./components/ChildArea";

export const App = () => {
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);

  const onChangeText = (e) => setText(e.target.value);

  const onClickOpen = () => setOpen(!open);

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

```react:src/components/ChildArea.jsx
export const ChildArea = (props) => {
  const { open } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
};
```
![③.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/e84bb78f-d85f-a490-808b-856fd800dcde.gif)

この場合`ChildArea`のpropsである`open`には触れていないが、親コンポーネントに再レンダリングが起きているため`ChildArea`にも再レンダリングが起きている。

# コンポーネント最適化
- memo
- useCallback

## memo(コンポーネントをmemo化)
```react:src/components/ChildArea.jsx
import { memo } from "react";

export const ChildArea = memo((props) => {
  const { open } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
});
```
![④.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/3fba204c-930a-389f-f850-80310340c843.gif)

子コンポーネントをmemo化することでpropsに変更があった時のみレンダリングが起きるようにできる。
基本的にコンポーネントは全てmemo化するべき。

## useCallback(関数をmemo化)
```react:src/App.jsx
import { useState } from "react";
import { ChildArea } from "./components/ChildArea";

export const App = () => {
  const [text, setText] = useState("");
  const [open, setOpen] = useState(false);

  const onChangeText = (e) => setText(e.target.value);

  const onClickOpen = () => setOpen(!open);

  // 追加
  const onClickClose = () => setOpen(false);

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

```react:src/components/ChildArea.jsx
import { memo } from "react";

export const ChildArea = memo((props) => {
  const { open, onClickClose } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
      <button onClick={onClickClose}>閉じる</button>
    </>
  );
});
```
![⑤.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/58cbddba-c99d-1ca4-0904-a615f8b2d3aa.gif)

今までの機能に表示を閉じるボタンを実装するために`onClickClose`という関数を追加した。
その場合、先程memo化したChildAreaには再度再レンダリングが起きるようになってしまう。
なぜなら、アロー関数で書いた関数は毎回新しい関数を生成しているという判断をされてしまい、propsが違うものとして扱われ、propsが更新された場合と同様に再レンダリングが起きてしまう。

### useCallbackを使う
useEffectと同様に第二引数に入れた値を監視するので今回は`setOpen`を指定して監視し`setOpen`の処理が走った時のみレンダリングが起きる。
結果、不要な再レンダリングを防げる。

```react
const onClickClose = useCallback(() => setOpen(false),[setOpen]);
```

![ezgif.com-gif-maker (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/0ef80c03-da8e-5780-b02a-9ef6447f192a.gif)


基本コンポーネントはmemo化しmemo化したコンポーネントに関数を使う時は、
その関数にuseCallbackを使うことで不要な再レンダリングを防げるのでレンダリングを最適化できる。

# 参考

https://www.udemy.com/course/react_stepup/