SuspenseとReact.lazyを使いパフォーマンス最適化

## はじめに
最近、WEBサイトのパフォーマンス最適化に興味があって、`ChatGPT`にパフォーマンス向上の方法を聞いてみました。
![chatGPT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1958304/cddd3a34-5c3f-95c4-da8a-8ff6d8da75c6.png)
なるほど～
**イメージの最適化**と、**コード分割**が手軽にはじめられそうな気がします（個人の主観です）
ということで、**パフォーマンス向上のために、コード分割をする方法**について紹介していきます。

## コード分割
コード分割をすると、ユーザーが必要としないコードを読み込まなくて済むため、初期ロードの際に読む込むコード量を削減できます。

### コード分割の方法
#### 1\.　import()

```react:Before
import { add } from './math';

console.log(add(16, 26));
```

```react:After
import("./math").then(math => {
  console.log(math.add(16, 26));
});
```

#### 2\.　React.lazy

```react:Before
import OtherComponent from './OtherComponent';
```

```react:After
const OtherComponent = React.lazy(() => import('./OtherComponent'));
```
このコンポーネントがはじめてレンダーされる時、`OtherComponent`を含むバンドルを自動的にロードしてくれます。

### 2つの方法の主な違い

* ダイナミックインポートを直接使用するやり方
    * モジュールの関数が直接呼び出されます。
* `React.lazy`を使用するやり方
    * ダイナミックインポートは`React.lazy`によって間接的に使用され、ロードされたモジュールは`Reactコンポーネント`としてレンダリングされます。

なお、`React.lazy`を使った場合、`Suspenseコンポーネント`を使って、非同期にロードされるコンポーネントのローディング状態を制御することができます。
次の章では、`Suspense`について説明していきます。

## Suspenseとは何か
### Suspenseの概要
`React`の`Suspense`を使うことで、コンポーネント内で非同期な操作（たとえば、データの取得やコードの分割）が行われる場合に、ローディング状態を管理することが可能になります。
`Suspense`コンポーネントの主な目的は、非同期な操作が完了するまでの間、特定のコンテンツ（通常はコンポーネント）を遅延させることです。
### フォールバックUIとの連携
`fallback`プロパティを使用してフォールバックUIを指定することができます。
フォールバックUIは、非同期な操作がまだ完了していない間に表示される、ローディングスピナーやローディングメッセージなどのコンポーネントのことです。ユーザーに操作の進行状況を伝え、待機中であることを明示する役割を果たします。

## React.lazyとSuspenseを組み合わせたコードスプリッティングの実装
### 実際のコードスプリッティングの例
```react:MyComponent.jsx
import React from 'react';

const MyComponent = () => {
  return <div>My Component</div>;
};

export default MyComponent;
```

```react:App.jsx
import React, { Suspense } from 'react';

const MyComponent = React.lazy(() => import('./MyComponent'));

const App = () => {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
    </div>
  );
};

export default App;
```


## エラーハンドリングの実装
### エラーバウンダリー(Error Boundaries)の利用
`Suspense`と`React.lazy`を使ったコードスプリッティングでは、エラーハンドリングは非常に重要な役割を果たします。特に、動的にロードされるコンポーネントがエラーを吐いた場合や、ネットワークエラーなどによりコンポーネントのロードが失敗した場合には、適切なエラーハンドリングが必要となります。

今回は、エラーハンドリングの手法としてエラーバウンダリーを紹介します。エラーバウンダリーは、子コンポーネントツリーで発生するエラーをキャッチし、それらをログに記録し、崩壊したコンポーネントツリーの代わりにフォールバックUIを表示する役割を果たします。

```react:ErrorBoundary.jsx
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
  // このメソッドはエラーが発生したら、stateを更新して次のレンダリングでフォールバックUIを表示できるようにします。
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // ここでエラー情報を出力します。
    console.error("An error occurred:", error, errorInfo);
    // ここに適切なエラーロギングサービスへの報告コードを書くことになります。例えば、Sentry.captureException(error);
  }

  render() {
    if (this.state.hasError) {
    // エラーが発生した場合、ここでフォールバックUIを描画できます。
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

export default ErrorBoundary;
```

```react:App.jsx
import React, { Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';

const MyComponent = React.lazy(() => import('./MyComponent'));

const App = () => {
  return (
    <div>
      <ErrorBoundary>
        <Suspense fallback={<div>Loading...</div>}>
          <MyComponent />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
};

export default App;
```


## まとめ
パフォーマンス最適化への取り組みの1つの手段として、今回は、「**SuspenseとReact.lazyを使いパフォーマンス最適化**」という記事を書きました。簡単に導入できる手法かつ、コンポーネントそのものを汚すことはないので、参考にしてみてください。