Reactの再レンダリングの仕組み

現在Reactを学んでいるWeb系への転職を目指している者です。
ポートフォリオをSPA化するために学んでいます。
自分用のメモのために残します。

#再レンダリングされるときの条件
1.stateが更新されたコンポーネントは再レンダリングされる。
2.propsが変更されたコンポーネントは再レンダリングされる。
3.再レンダリングされたコンポーネント配下の子要素はすべて再レンダリングされる。

大きくこの3つの条件でレンダリングされます。
検証のためにはcodesandboxを使用します。

#1.stateが更新されたコンポーネントは再レンダリングされる。

再レンダリングされるとconsole.logでレンダリングされましたというのが表示されるようなコードを作成しました。

```react:App.jsx
import "./styles.css";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};
export default function App() {
  const [text, setText] = useState("");
  const onChangeText = (event) => setText(event.target.value);

  console.log("レンダリング");
  return (
    <>
      <div style={style}>
        <input
          style={textBox}
          value={text}
          onChange={onChangeText}
          placeholder="文字を入力してください"
        />
      </div>
    </>
  );
}
```
このコードではテキストボックスに文字入力されるたびにstateが更新されます。
画面を表示した状態ではレンダリングは1回されているので、コンソールにはレンダリングの文字が1回表示されます。
![スクリーンショット 2022-01-30 20.52.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/32f2e2fc-7980-b905-a0d0-f03493583290.png)
今回は文字を5回入力したので、stateが5回更新されます。
表示したときのものと合わせて計6回コンソールにレンダリングの文字が表示されます。
![スクリーンショット 2022-01-30 20.53.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/8b4ec6ea-4124-3c7e-d910-64d90d7efe7d.png)

#2.propsが更新されたコンポーネントは再レンダリングされる。
先ほどのテキストボックスをコンポーネント化しました。
改修したコードはこちらになります。

```react:TextBox.jsx
const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};

export const TextBox = (props) => {
  const { text, onChangeText } = props;
  console.log("TextBoxコンポーネントが更新された");
  return (
    <div style={style}>
      <input
        style={textBox}
        value={text}
        onChange={onChangeText}
        placeholder="文字を入力してください"
      />
    </div>
  );
};
```

```react:App.jsx
import "./styles.css";
import { TextBox } from "./components/TextBox";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

export default function App() {
  const [text, setText] = useState("");
  const onChangeText = (event) => setText(event.target.value);

  console.log("レンダリング");
  return (
    <>
      <h1 style={style}>レンダリングの条件を理解する</h1>
      <TextBox text={text} onChangeText={onChangeText} />
    </>
  );
}
```
この状態で文字を入力すると子コンポーネントであるTextBox.jsxに記述した「TextBoxコンポーネントがレンダリングされた」というコンソールも表示されていることがわかります。
![スクリーンショット 2022-01-30 21.14.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/6e4da7a1-a1bb-78cf-0807-db4b295ff01e.png)

#3.再レンダリングされたコンポーネント配下の子要素はすべて再レンダリングされる。
ここからが注意しなければならない部分になります。
まずは新たなコンポーネントを作成します。

```react:ChildArea.jsx
const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};

export const TextBox = (props) => {
  const { text, onChangeText } = props;
  console.log("TextBoxコンポーネントがレンダリングされました");
  return (
    <div style={style}>
      <input
        style={textBox}
        value={text}
        onChange={onChangeText}
        placeholder="文字を入力してください"
      />
    </div>
  );
};
```

新たなコンポーネント作成に伴い既存のコードも改修したので、載せます。

```react:TextBox.jsx
const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};

export const TextBox = (props) => {
  const { text, onChangeText } = props;
  console.log("TextBoxコンポーネントがレンダリングされました");
  return (
    <div style={style}>
      <input
        style={textBox}
        value={text}
        onChange={onChangeText}
        placeholder="文字を入力してください"
      />
    </div>
  );
};
```

```react:App.js
import "./styles.css";
import { TextBox } from "./components/TextBox";
import { ChildArea } from "./components/ChildArea";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const buttonStyle = {
  margin: "20px",
  padding: "20px"
};

export default function App() {
  const [text, setText] = useState("");
  const [display, setDisplay] = useState(false);
  const onChangeText = (event) => setText(event.target.value);
  const onClickChanged = () => setDisplay(!display);
  console.log("レンダリング");
  return (
    <>
      <h1 style={style}>レンダリングの条件を理解する</h1>
      <TextBox text={text} onChangeText={onChangeText} />
      <button style={buttonStyle} onClick={onClickChanged}>
        表示
      </button>
      <br />
      <ChildArea display={display} />
    </>
  );
}
```

これにより表示ボタンを押すと画像が表示される機能が追加されました。

画像を表示させる。
![スクリーンショット 2022-01-31 17.13.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/79286040-aa8d-8509-0c2f-10c292cc4994.png)

画像を非表示させる。
![スクリーンショット 2022-01-31 17.13.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/7b482376-3d56-c17e-87e9-d9682242a4ce.png)

ここでの問題というのは再レンダリングされたコンポーネントの子コンポーネントはすべて再レンダリングされてしまいます。
コンソールを確認してみると。

・文字をテキストボックスに入力した場合
![スクリーンショット 2022-01-31 17.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/fc898014-1c0a-19ee-a4f6-0b521f77baa7.png)

・表示ボタンをクリックした場合
![スクリーンショット 2022-01-31 17.17.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/3e1d1fa0-0a35-2931-a99d-6b9df30ac770.png)

どちらも関係ないコンポーネントまで再レンダリングされてしまいます。
要するにすべてのコンポーネントが呼び出されています。





#子要素がすべて再レンダリングされる場合の解決方法。
コンポーネントをmemoで囲む。
memoで囲まれた関数はpropsが渡された時のみ、再レンダリングされるという指定がされます。

#memo化の方法

```react:ChildArea.jsx
// memo化に必要
import { memo } from "react";
const imageStyle = {
  height: "100px",
  width: "100px"
};

// 関数をmemo()で囲んであげればmemo化が完成。
export const ChildArea = memo((props) => {
  const { display } = props;
  console.log("ChildAreaがレンダリングされました");
  return (
    <>
      {display ? (
        <img style={imageStyle} src="images/account.jpeg" alt="Logo" />
      ) : null}
    </>
  );
});

```

今回はchildAreaコンポーネントをmemo化しました。
これで文字を入力した場合にChildAreaコンポーネントが再レンダリングされることはないはずです。
確認してみると。![スクリーンショット 2022-01-31 17.26.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/c546848e-042c-f9c5-c43f-a89f8f9aa4be.png)

試しの文字を二回入力しました。
ページが開かれた際に呼び出されたのみで、再レンダリングはされていないことがわかります。
これでpropsが渡されたときのに再レンダリングされる機能の実装ができました。


#memo化の注意点
memo化したコンポーネントに関数を渡す場合は、渡す関数は再レンダリングの際に再生成されるpropsと判断されるためmemo化がうまく機能しなくなります。
検証するために表示ボタンもChildAreaコンポーネントに組み込みます。

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

const imageStyle = {
  height: "100px",
  width: "100px"
};
const buttonStyle = {
  margin: "20px",
  padding: "20px"
};

export const ChildArea = memo((props) => {
  const { display, onClickChanged } = props;
  console.log("ChildAreaがレンダリングされました");
  return (
    <>
      <button style={buttonStyle} onClick={onClickChanged}>
        表示
      </button>
      <br />
      {display ? (
        <img style={imageStyle} src="images/account.jpeg" alt="Logo" />
      ) : null}
    </>
  );
});
```

```react:App.js
import "./styles.css";
import { TextBox } from "./components/TextBox";
import { ChildArea } from "./components/ChildArea";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

export default function App() {
  const [text, setText] = useState("");
  const [display, setDisplay] = useState(false);
  const onChangeText = (event) => setText(event.target.value);
  const onClickChanged = () => setDisplay(!display);
  console.log("レンダリング");
  return (
    <>
      <h1 style={style}>レンダリングの条件を理解する</h1>
      <TextBox text={text} onChangeText={onChangeText} />
      {/* 表示ボタンをChildAreaコンポーネントに追加したため関数をpropsとして渡しています。 */}
      <ChildArea display={display} onClickChanged={onClickChanged} />
    </>
  );
}

```

このコードで注目して欲しいのは、ChildAreaコンポーネントに関数をpropsとして渡しているということです。
関数をpropsでコンポーネントに渡すとmemo化がうまく働かなくなります。
試しに文字を入力した際のコンソールを確認してみます。
![スクリーンショット 2022-01-31 17.40.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/d8bdc24e-61ca-cc4f-904f-40d667bc29e5.png)

memo化したはずなのに再レンダリングが起きてしまっています。

#関数をpropsで渡した際にmemo化がうまく動いていない原因

原因は新たなpropsが渡されていると判断されてしまっているからです。
なぜなら、関数はレンダリングされるたびに再生成されていると判断されているため、同じ名前の関数でもレンダリングされる前とは別の関数として認識されてしまうからです。
そのため、memoがうまく機能しません。


##改善方法
useCallBackを使用する。
memo化したコンポーネントに渡す関数を、useCallBackで囲むことで、第二引数に指定した配列の値が変化する時のみ再生成されると設定できる。
配列が空の場合は最初のレンダリング時から再生成されなくなります。
それでは、useCallbackで関数を囲みます。

```react:App.js
const onClickChanged = useCallback(() => setDisplay(!display), [display]);
```

これにより変数であるdisplayが変化する時のみonClickChangedが再生成されるように設定できました。
それではまた、試しに文字を入力した際のコンソールを確認してみます。
![スクリーンショット 2022-01-31 17.52.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571061/33452395-f426-ec81-88fe-cd931427d830.png)
今回はしっかりとmemo化が行われていることがわかります。

#まとめ
Reactを扱う際に基本的な部分だったと思いますが、基本が大切だと思うのでメモを残しました。
この記事が私と同じくReactを勉強し始めた人の助けになったらうれしいです。