useMemoで無駄な再実行を防ぎ、サイトのパフォーマンスを向上させる

# はじめに
ReactのuseMemoフックを使うことで、不必要な処理の再実行を避け、サイトのパフォーマンスを向上させることができます。本記事では、useMemoを使わない場合と使う場合の2つのパターンを比較し、実際の処理時間の違いを確認していきます。


# 環境
- React：18.3.1
- vite：5.3.4

# useMemoとは？
useMemoを使用すると処理の結果などをキャッシュ（メモ化）し、依存関係をもつ値が変更されない限り対象の処理の再実行を避けることができます。不必要な重い処理をuseMemoでラップするなどの使い方により、パフォーマンスを上げることができます。


# 使い方

```tsx
const memoizedValue = useMemo(() => {
   処理を記述
}, [依存する値]);
```


# 重い計算を行ってみる

下記のような重い計算処理は必要がなければ実行させたくないです。

```tsx
// ～～～
// 重い処理
const calculate = (num: number) => {
  let result = 0;
  for (let i = 0; i < 1000000; i++) {
    result += i * num;
  }
  return result;
};
// ～～～
```

状態変数`num`をuseMemoの依存関係にしてみます。
上記の重い計算処理をラップした場合`num`に変化がなければ重たい処理は再実行がされませんが、useMemoを使わない場合は画面が再描画されるたびに処理の再実行が走ってしまいます。

```tsx:
//～～～
// useMemoを使わない場合
const CalcWithoutMemo = ({ num }: { num: number }) => {
  const start = performance.now();
  const result = calculate(num);
  const end = performance.now();
  return (
    <>
      <p>・Result: {result}</p>
      <p>・実行時間: {end - start} ms</p>
    </>
  );
};

// useMemoを使った場合
const CalcWithMemo = ({ num }: { num: number }) => {
  const start = performance.now();
  const result = useMemo(() => calculate(num), [num]);
  const end = performance.now();
  return (
    <>
      <p>・Result: {result}</p>
      <p>・実行時間: {end - start} ms</p>
    </>
  );
};
//～～～
```

App.tsx全体のソースと、使用しているCSSは下記の通りです。

<details><summary>App.tsx</summary>

```tsx:App.tsx
import { useState, useMemo } from 'react';
import './App.css';

// 重い処理
const calculate = (num: number) => {
  let result = 0;
  for (let i = 0; i < 1000000; i++) {
    result += i * num;
  }
  return result;
};

// useMemoを使わない場合
const CalcWithoutMemo = ({ num }: { num: number }) => {
  const start = performance.now();
  const result = calculate(num);
  const end = performance.now();
  return (
    <>
      <p>・Result: {result}</p>
      <p>・実行時間: {end - start} ms</p>
    </>
  );
};

// useMemoを使った場合
const CalcWithMemo = ({ num }: { num: number }) => {
  const start = performance.now();
  const result = useMemo(() => calculate(num), [num]);
  const end = performance.now();
  return (
    <>
      <p>・Result: {result}</p>
      <p>・実行時間: {end - start} ms</p>
    </>
  );
};

const App: React.FC = () => {
  const [num, setNum] = useState(1);
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setNum(prev => prev + 1)}>Numを+1する</button>
      <button onClick={() => setCount(prev => prev + 1)}>countを+1する</button>
      <div>
        <h1>useMemoを使ってない場合</h1>
        <CalcWithoutMemo num={num} />
      </div>
      <br />
      <div>
        <h1>useMemoを使った場合</h1>
        <CalcWithMemo num={num} />
      </div>
      <br />
      <div>レンダリング回数： {count}</div>
    </>
  );
};

export default App;
```

</details>


<details><summary>App.css</summary>

```css:App.css
body {
  height: 100vh;
  width: 100vw;
  padding: 0;
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}
```

</details>



## 実行結果

サーバを起動しブラウザページに実行時間を表示します。

```
npm run dev
```

表示されるページには、2つのボタン（`Numを+1する` と `countを+1する`）を設置しています。
- `Numを+1する`ボタンを押下した場合：
useMemoの依存関係に指定している状態変数が更新されるので、`CalcWithoutMemo ` コンポーネントと `CalcWithMemo` コンポーネントのどちらとも重い計算（calculate）が実行されます。

- `countを+1する`ボタンを押下した場合：
useMemoの依存関係に指定している状態変数（`num`）は更新されないので、`CalcWithoutMemo` コンポーネントでは重い計算（calculate）が実行されますが、`CalcWithMemo` コンポーネントでは実行されません。


```tsx
    <>
      //～～～
      <button onClick={() => setNum(prev => prev + 1)}>Numを+1する</button>
      <button onClick={() => setCount(prev => prev + 1)}>countを+1する</button>
      <div>
        <h1>useMemoを使ってない場合</h1>
        <CalcWithoutMemo num={num} />
      </div>
      <br />
      <div>
        <h1>useMemoを使った場合</h1>
        <CalcWithMemo num={num} />
      </div>
      //～～～
    </>
```

# 実行結果の比較

初回レンダリングは実行時間に少し違いがみられますがどちらも実行されてる事が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/5a5f6223-ec88-96c4-4cfd-186e1599018e.png)


`Numを+1する`ボタンを押下した場合、どちらも実行されてる事が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/c2f01b2c-157a-7a16-3250-97dc2b7f6145.png)

`countを+1する`ボタンを押下した場合は、useMemoを使用した方は依存関係を持つ値が変化していないため重い計算（calculate）は実行さてない事が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/0937e8d6-8541-daec-c88c-eecc442e49d4.png)