Reactについて学んだことをまとめる（その２）

以下のUdemyの講座を受講したので、学んだことをまとめていく。

https://www.udemy.com/course/modern_javascipt_react_beginner/?couponCode=KEEPLEARNING

## レンダリングの最適化
再レンダリングされる条件と再レンダリングによるパフォーマンス低下が起こる例、及びそれを解消するための方法について学んだ。

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

```jsx
//App.jsx
import './App.css';
import { ChildArea } from './ChildArea';

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

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

  const onClickCountUp = () => {
    setConut(count + 1);
  };

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

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

```jsx
//ChildArea.jsx
export const ChildArea = (props) => {
  const { open } = props;
  console.log('ChildAreaがレンダリングされた！');

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log('...');
  });

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子こんぽーねんと</p>
        </div>
      ) : null}
    </>
  );
};
```

上記の例では、「表示」ボタンを押すと`ChildArea`に渡されている`open`というPropsが更新されるので、`ChildArea`コンポーネントは再レンダリングされる。
テキストボックスが更新された場合、`text`ステートが更新されるので、`App`コンポーネントが再レンダリングされる。
その際、`ChildArea`コンポーネントは`App`コンポーネントの子要素にあたるため、`ChildArea`コンポーネントも再レンダリングされてしまう。

### memo関数

`memo`という関数を使うと、Propsが変更された時のみ再レンダリングするようにできる。
コンポーネントを`memo`関数で囲むように使用する。

```jsx
//ChildArea.jsx
export const ChildArea = memo((props) => {
  const { open } = props;
  console.log('ChildAreaがレンダリングされた！');

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log('...');
  });

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子こんぽーねんと</p>
        </div>
      ) : null}
    </>
  );
});
```
この場合、表示ボタンを押すとPropsが更新されるので再レンダリングされる。
テキストボックスが更新された場合は親コンポーネントの再レンダリングが起きるが、`memo`関数により、`ChildArea`コンポーネントは再レンダリングされない。

### useCallback関数
下記の例では、Propsに`onClickClose`という関数を渡しているが、**Propsに毎回新しい関数を生成して渡している扱いになるため、`ChildArea`コンポーネントの再レンダリングが発生する。**

```jsx
//App.jsx
import { useState } from 'react';
import './App.css';
import { ChildArea } from './ChildArea';

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

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

  const onClickClose = () => setOpen(false);

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

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

```jsx
//ChildArea.jsx
import { memo } from 'react';

const style = {
  width: '100%',
  height: '200px',
  backgroundColor: 'khaki',
};

export const ChildArea = memo((props) => {
  const { open, onClickClose } = props;
  console.log('ChildAreaがレンダリングされた！');

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log('...');
  });

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子こんぽーねんと</p>
          <button onClick={onClickClose}>閉じる</button>
        </div>
      ) : null}
    </>
  );
});
```

`useCallback`関数を使用すると、条件を満たした場合のみ再生成するようにできる。
これにより、関数の再生成による再レンダリングを抑制できる。

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

使い方は`useEffect`と同様、第1引数に関数の処理を記述する。
`useEffect`の第2引数に空配列を指定すると、マウント時(ページが最初に読み込まれる時)に1度だけ生成される。
第2引数の配列要素に変数を指定すると、指定した変数が更新された場合に第1引数に記述した関数が再生成される。