【React】コード分割による表示パフォーマンス改善

Create-React-Appのプロジェクトをビルドする際、プロジェクトが大きいとWebpackでバンドルされたファイルも大きくなってしまいます。
バンドルファイルのサイズが大きいと表示パフォーマンスに影響がでてしまい、ReactでSPAを作成した意味が薄れてしまいます。

そんな問題を解決するために、コード分割(Code Splitting)という方法が使われているということを知りました。

今のところ現場で使うことはなさそうなのですが、今後の仕事のために備忘録としてまとめました。

#Create-React-Appプロジェクトのビルド
Create-React-Appで生成したプロジェクトでビルドを実行すると、buildフォルダにいろんなファイルが出力されます。
publicフォルダの内容がbuildフォルダにコピーされているのですが、`build/index.html`の中身をみると、`public/index.html`には記述されていなかったscriptタグがbodyタグの末尾に挿入されています。
つまり、ウェブサーバにbuildフォルダを配置したとき、なにかしらのjsファイルが読み込まれReactのコンポーネントが表示されるようになっています。

読み込まれるjsファイル(`static/js/`の中身)には以下のものがあります。

- `[number].[hash].chunk.js`: srcディレクトリ配下でインポートしたnode_modulesのコードが含まれます。
- `main.[hash].chunk.js`: App.jsなどのsrcディレクトリ配下のアプリケーションのコード含まれます。

dev-toolのNetworkタブをみてみると、こんな感じで読み込まれています。
![スクリーンショット 2021-05-09 8.53.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/ccc584f7-2c2e-5b62-4a4d-8450adba5455.png)

#コード分割(Code Splitting)
コード分割は、バンドルファイルを分けてページロードを早くするための手法です。

コード分割を行っていない場合、ページを開いたときにプロジェクトのすべてのコード(=表示しないページを含んだコード)が含まれたバンドルファイルを読み込んでいます。
つまり、表示に関係のある部分のコードを別ファイルに分割して読み込めれば、その分表示が早くなることになります。

#React.lazy
コード分割を行うために、React.lazy関数を使用します。
この関数を使うことで、動的インポートを通常のコンポーネントとしてレンダーすることができます。

```jsx
//動的インポート
import HomePage from './pages/homepage/homepage.component';
```

```jsx
//React.lazy
const HomePage = lazy(() => import('./pages/homepage/homepage.component'));
```
例えば、以下の'/'パスを開いてみると、React.lazyの`HomePage`の場合では、レンダー時点で`HomePage`を除いたバンドルファイル(main.chunk.js)を読み込みます。
その後に、`<Route exact path="/" component={HomePage} />`で`HomePage`のバンドルファイル(`0.chunk.js`)が読み込まれます。

```jsx
return (
      <div>
        <GlobalStyle />
        <Header />
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route path="/shop" component={ShopPage} />
          <Route exact path="/checkout" component={CheckoutPage} />
          <Route
            exact
            path="/signin"
            render={() =>
              props.currentUser ? (
                <Redirect to="/" />
              ) : (
                <SignInAndSignUpPage />
              )
            }
          />
        </Switch>
      </div>
    );

```

ただ、React.lazy関数(Promiseを返す)をそのまま読み込もうとするとエラーがでます。
![スクリーンショット 2021-05-08 9.07.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/1746e8d8-2af4-211c-61a4-490ef2b69a43.png)

そのため、`HomePage`を取得するまでの間は`loading`を出すように、`Suspense`でラッピングしてあげます。

```jsx
          <Suspense fallback={<div>...loading</div>}>
            <Route exact path="/" component={HomePage} />
          ​</Suspense>
```

また、`HomePage`以外のページについても分割するときは、`Suspense`をラッピングする範囲を変えれば大丈夫です。

```jsx
const HomePage = lazy(() => import('./pages/homepage/homepage.component'));
const ShopPage = lazy(() => import('./pages/shop/shop.component'));
const SignInAndSignUpPage = lazy(() =>
  import('./pages/sign-in-and-sign-up/sign-in-and-sign-up.component')
);
const CheckoutPage = lazy(() => import('./pages/checkout/checkout.component'));
```

```jsx
return (
      <div>
        <GlobalStyle />
        <Header />
        <Switch>
          <ErrorBoundary>
            <Suspense fallback={<div>...loading</div>}>
              <Route exact path="/" component={HomePage} />

              <Route path="/shop" component={ShopPage} />
              <Route exact path="/checkout" component={CheckoutPage} />
              <Route
                exact
                path="/signin"
                render={() =>
                  props.currentUser ? (
                    <Redirect to="/" />
                  ) : (
                    <SignInAndSignUpPage />
                  )
                }
              />
            </Suspense>
          </ErrorBoundary>
        </Switch>
      </div>
    );
```

#Error boundary
以上のコードで`Suspense`をさらに`ErrorBoundary`でラッピングしていますが、これでコンポーネント内で発生したJavaScriptエラーが起こったときに、フォールバック用のUIを表示させることができます。

#おわりに
むやみやたらとコード分割をするのは悪手で、dev-toolなどで現状のパフォーマンスを計測してから最適化は行うべきじゃないとのことです。

https://qiita.com/official-events/b9ad63394fa2635dfca9

#参考資料
https://ja.reactjs.org/docs/code-splitting.html

https://qiita.com/mizchi/items/418be9abee5f785696f0

https://qiita.com/seya/items/06b160adb7801ae9e66f

https://hand-dot.com/blog/start-react-app-with-create-react-app/

https://www.cyokodog.net/blog/code-splitting-and-async-component/

https://ja.reactjs.org/docs/error-boundaries.html

https://kudolog.net/posts/func-error-boundary/