Reactの再レンダリングについて

## 前置き
Reactを学習し始めたときに理解が浅かったmemo化についてまとめてみました。

## 再レンダリングとは
**変更を検知してコンポーネントを再処理すること**です。
画面をリロードしたわけでもないのに、処理によって画面が変更されるのは、コンポーネントが再レンダリングされている為です。

![ezgif.com-gif-maker (14).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/63660b6f-8524-9f28-bfeb-6cdc7e5d0969.gif)


## 再レンダリングが起きる条件
1. stateが更新された時
2. Propsが変更された時
3. 再レンダリングされたコンポーネント配下の子コンポーネント全て

3は中々イメージがつきにくいですが、以下の例のようにルートコンポーネントであるApp.jsxがレンダリングされるとその配下のコンポーネントが全てレンダリングされてしまいます。
変更する必要のないコンポーネントまでレンダリングされてしまうと、アプリ自体のパフォーマンスが落ちてしまいます。
![スクリーンショット 2021-10-19 20.06.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/a30b81a8-1a17-13ec-9881-5eba29da052b.png)


# React.memo, useCallbackを使って不要なレンダリングを防ぐ
ここから実際にコードを使って解説します。今回の例では、App.jsを親、子コンポーネントをChild.jsxとします。

```react
// App.js
import { useState } from "react";
import Child from "./Child";
import "./styles.css";

export default function App() {
  const [number, setNumber] = useState(0);
  const onClickNumber = () => setNumber(number + 1);
  console.log("レンダリングしてます");
  return (
    <div className="App">
      <button onClick={onClickNumber}>ボタン</button>
      <h1>{number}</h1>
      <Child />
    </div>
  );
}

```

```react
// Child.jsx
const Child = () => {
  return (
    <>
      {console.log("child")}
      <div>childです</div>
    </>
  );
};
export default Child;

```


親コンポーネントにあるボタンをクリックすると、子供であるChild.jsxに仕込んだconsole.logもクリックする度に発火してしまいます。
不要なレンダリングが起きているので、子コンポーネントは変更があった場合のみレンダリングさせたいです。

![ezgif.com-gif-maker (7).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/50dbadeb-6d18-1ceb-2b1c-d9e02347e4d8.gif)

## React.memo
コンポーネント自体をmemoで囲うだけで**propsに変更がない限りそのコンポーネントで再レンダリングが起きなくなります。**

```react
// child.jsx

// 追加
import { memo } from "react";

// memoで囲うだけ！
const Child = memo(() => {
  return (
    <>
      {console.log("child")}
      <div>childです</div>
    </>
  );
});
export default Child;

```
ボタンをクリックしても子コンポーネントにあるconsole.logが発火しなくなりました。

![ezgif.com-gif-maker (8).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/961951b9-3b5d-f2c8-b260-c61cfa8860a4.gif)


## useCallback
**子コンポーネントにアロー関数を渡すと、memo化していてもレンダリングされるようになります。**
これをuseCallbackを活用することで**一度定義したアロー関数が更新されない限り、同じものを使い回すという処理を実現できます。**

```react
// 書き方
useCallback(アロー関数, [依存配列]);
```

試しにボタンをクリックすると、子コンポーネントを表示非表示するような実装に変更します。

![ezgif.com-gif-maker (10).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/61eba00f-fe75-1cff-c289-bc320b5e83d8.gif)

その際、親コンポーネントにあるアロー関数をpropsで渡してあげます。

```react
// App.js
import { useState } from "react";
import Child from "./Child";
import "./styles.css";

export default function App() {
  const [number, setNumber] = useState(0);
  const [show, setShow] = useState(false);
  const onClickNumber = () => setNumber(number + 1);
  // 追加
  const onClickShow = () => setShow(!show);
  const onClickClose = () => setShow(false);

  return (
    <div className="App">
      <button onClick={onClickNumber}>ボタン</button>
      <h1>{number}</h1>
      <button onClick={onClickShow}>show/hide</button>
      {/* アロー関数をpropsで渡す */} 
      <Child show={show} onClickClose={onClickClose} />
    </div>
  );
}

```

```react
// Child.jsx
import { memo } from "react";

const Child = memo((props) => {
  const { show, onClickClose } = props;
  return (
    <>
      {console.log("child")}
      {show ? (
        <>
          <div>childです</div>
          <button onClick={onClickClose}>閉じる</button>
        </>
      ) : null}
    </>
  );
});
export default Child;

```

コンソールを見ると子コンポーネントにあるconsole.logが再び発火してることが分かります。
memo化したにも関わらず、親コンポーネントにあるボタンをクリックすると、関係のない子コンポーネントも再びレンダリングされるようになってしまいました。
![ezgif.com-gif-maker (9).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/647facfa-9ca7-2f42-b418-1dd192177a66.gif)

これはアロー関数が**レンダリングされる度に再生成されるので、毎回違う関数として認識されてしまいます。**
その為、propsが変わっていると判断されるので子コンポーネントも再レンダリングがおきるようになります。

処理の流れとしては以下のようになります。
1. 親にあるボタンをクリックする
２. stateが変更される為、親コンポーネントがレンダリングされる
3. レンダリングすると親コンポーネントのアロー関数が再生成される
4. 再生成したアロー関数を子コンポーネントに受け渡す
５. propsが変更されていると判断される為、子コンポーネントもレンダリングされる

これをuseCallbackを使うことで、不要なレンダリングを防ぐことができます。

```react
// App.js
// 追加
import { useState, useCallback } from "react";
import Child from "./Child";
import "./styles.css";

export default function App() {
  const [number, setNumber] = useState(0);
  const [show, setShow] = useState(false);
  const onClickNumber = () => setNumber(number + 1);
  const onClickShow = () => setShow(!show);
// 追加
  const onClickClose = useCallback(() => setShow(false), [setShow]);

  return (
    <div className="App">
      <button onClick={onClickNumber}>ボタン</button>
      <h1>{number}</h1>
      <button onClick={onClickShow}>show/hide</button>
      <Child show={show} onClickClose={onClickClose} />
    </div>
  );
}

```
これで不要なレンダリングを防ぐことができました!

![ezgif.com-gif-maker (11).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1185431/d8d12528-0709-4967-c276-0c47113d236c.gif)