8
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-05-09

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

コード分割(Code Splitting)

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

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

React.lazy

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

//動的インポート
import HomePage from './pages/homepage/homepage.component';
//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)が読み込まれます。

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

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

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

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

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'));
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などで現状のパフォーマンスを計測してから最適化は行うべきじゃないとのことです。

参考資料

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4