React.memoで無駄な再レンダリングを行わないようにする

# Reactの木構造とrender
- stateが変わると再レンダリングされる
- 親のstateが変わると全ての子が再レンダリングされる

# React.memoの活用
- 親のstateが変わっても子に渡すpropsの値に変化がなければ再レンダリングしない

# React.memoの基本構文
- コンポーネントごとに設定する

```typescript
import React, {FC} from 'react';

interface Props {
  trigger: number
}

// React.memo()でラップする
const MemoChild: FC<Props> = React.memo(({trigger}) => {
  // なんらかの重い処理
  return (
    <div>Memo Component: {trigger}</div>
  )
})

export default MemoChild;
```

# サンプルコードで動作確認

https://github.com/shtwangy/react-memo-test

```App.tsx
import React, {FC, useState} from 'react';
import {SfcChild, MemoChild, DeepEqualMemoChild} from "./components";

export interface Obj {
  deepTrigger: number
}

const App: FC = () => {
  const [sfcTrigger, setSfcTrigger] = useState<number>(0)
  const [memoTrigger, setMemoTrigger] = useState<number>(0)

  const countUpSfc = () => {
    setSfcTrigger(prevState => prevState + 1)
  }

  const countUpMemo = () => {
    setMemoTrigger(prevState => prevState + 1)
  }

  return (
      <div>
        <h2>React Memo Test</h2>
        <button onClick={countUpSfc}>SFC</button>
        <button onClick={countUpMemo}>Memo</button>

        <SfcChild trigger={sfcTrigger} />
        <MemoChild trigger={memoTrigger} />
      </div>
  );
};

export default App;
```

```src/components/StatelessFunctionalComponent.tsx
import React, {FC} from 'react';

interface Props {
    trigger: number
}

const StatelessFunctionalComponent: FC<Props> = ({trigger}) => {
    const startTime = performance.now()

    for (let i=0; i < 10000; i++) {
        // 16桁の文字列を乱数生成
        const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        const N=16
        Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')
    }

    const endTime = performance.now()

    console.log(`SFC: ${endTime - startTime} milliseconds`)

    return (
        <div>Stateless Functional Component: {trigger}</div>
    );
};

export default StatelessFunctionalComponent;
```

```src/components/ReactMemoComponent.tsx
import React, {FC} from 'react';

interface Props {
    trigger: number
}

const ReactMemoComponent: FC<Props> = React.memo(({trigger}) => {
    const startTime = performance.now();

    for (let i=0; i < 10000; i++) {
        // 16桁の文字列を乱数生成
        const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        const N=16
        Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')
    }

    const endTime = performance.now()

    console.log(`Memo: ${endTime - startTime} milliseconds`)

    return (
        <div>React Memo Component: {trigger}</div>
    )
})

export default ReactMemoComponent;
```

コンソールログを確認しながら、各ボタンを押して動作を確認する。
SFCボタンを押す
→`StatelessFunctionalComponent`のログだけ流れてくる
→`StatelessFunctionalComponent`だけ再描画されている（`ReactMemoComponent`は再描画されていない）

Memoボタンを押す
→`StatelessFunctionalComponent`と`ReactMemoComponent`のログが流れてくる
→`StatelessFunctionalComponent`と`ReactMemoComponent`の両方が再描画されている


# Deep Equalの活用
- 比較関数を書く
- trueを返すとレンダリングしない
- falseを返すとレンダリングする

# サンプルコードで動作確認
```App.tsx
import React, {FC, useState} from 'react';
import {SfcChild, MemoChild, DeepEqualMemoChild} from "./components";

export interface Obj {
  deepTrigger: number
}

const App: FC = () => {
  const [sfcTrigger, setSfcTrigger] = useState<number>(0)
  const [memoTrigger, setMemoTrigger] = useState<number>(0)
  const [obj, setObject] = useState<Obj>({deepTrigger: 0})

  const countUpSfc = () => {
    setSfcTrigger(prevState => prevState + 1)
  }

  const countUpMemo = () => {
    setMemoTrigger(prevState => prevState + 1)
  }

  const countUpDeepTrigger = () => {
    setObject(prevState => {
      return {deepTrigger: prevState.deepTrigger + 1}
    })
  }

  return (
      <div>
        <h2>React Memo Test</h2>
        <button onClick={countUpSfc}>SFC</button>
        <button onClick={countUpMemo}>Memo</button>
        <button onClick={countUpDeepTrigger}>Deep Equal Memo</button>

        <SfcChild trigger={sfcTrigger} />
        <MemoChild trigger={memoTrigger} />
        <DeepEqualMemoChild trigger={memoTrigger} obj={obj}/>
      </div>
  );
};

export default App;
```

```src/components/DeepEqualMemoComponent.tsx
import React, {FC} from 'react';
import {Obj} from "../App";

interface Props {
    trigger: number
    obj: Obj
}

const DeepEqualMemoComponent: FC<Props> = React.memo(({trigger, obj}) => {
    const startTime = performance.now()

    for (let i=0; i < 10000; i++) {
        // 16桁の文字列を乱数生成
        const S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        const N=16
        Array.from(Array(N)).map(()=>S[Math.floor(Math.random()*S.length)]).join('')
    }

    const endTime = performance.now()

    console.log(`Deep Equal Memo: ${endTime - startTime} milliseconds`)

    return (
        <div>Deep Equal Memo Component: {trigger}</div>
    )
}, (prevProps: Props, nextProps: Props) => {
    const prevDeepTrigger = prevProps.obj.deepTrigger
    const nextDeepTrigger = nextProps.obj.deepTrigger
    return (prevDeepTrigger === nextDeepTrigger)
})

export default DeepEqualMemoComponent;
```

SFCボタンを押す
→`StatelessFunctionalComponent`のログだけ流れてくる
→`StatelessFunctionalComponent`だけ再描画されている
→`ReactMemoComponent`、`DeepEqualMemoComponent`は再描画されていない

Memoボタンを押す
→`StatelessFunctionalComponent`と`ReactMemoComponent`のログが流れてくる
→`StatelessFunctionalComponent`と`ReactMemoComponent`が再描画されている
→**`DeepEqualMemoComponent`は再描画されていない**
→`memoTrigger`も受け取っているが、`obj`の変更を検知しない限り再レンダリングは行わない

Deep Equal Memoボタンを押す
→`StatelessFunctionalComponent`と`DeepEqualMemoComponent`のログが流れてくる
→`StatelessFunctionalComponent`と`DeepEqualMemoComponent`が再描画されている
→`ReactMemoComponent`は再描画されていない