7839

雑草魂エンジニアブログ

【React】React.lazy + React.Suspense + React Routerを使ったローディング画面実装とコード分割

React の SPA を実装する際に、画面遷移時にローディング画面を挿入したいことがある。今回、React v16.6 から利用可能になったReact.lazy と React.Suspense を初めて使ってみたので、備忘録として残しておく。

React.lazy とは

React.lazy を使用することで、動的に読み込んだコンポーネントを通常のコンポーネントとしてレンダリングすることができるようになる。

// 従来のimport
import OtherComponent from './OtherComponent'
// React.lazy + Dynamic import
const LazyComponent = React.lazy(() => import('./OtherComponent'))

従来の import は、JavaScriptのトップレベルでしか用いることができず、実行時に即座にモジュールを読み込んでいた。

Dynamic import(動的読み込み)の場合は、import(<--module-->): Promise を用いて、動的に読み込む。返り値は Promise である。

そして、React.lazyコンポーネント(遅延コンポーネント)を返す関数であり、動的インポート構文 import() を呼び出す関数を引数として取る。ただし、import() で読み込まれるモジュールは、コンポーネントdefault export でエクスポートしている必要がある。React.lazy のおかげで、通常のコンポーネント同様に、動的に読み込んだコンポーネントを扱えるようになる。

React で作成した SPA は webpack により全てのコードが1つのJavaScript ファイル(bundle.js等)にバンドルされる。規模の小さい SPA であれば関係ないが、大規模な SPA の場合、初期表示に読み込む JavaScript ファイル が増加していく問題がある。特に、サイズの大きなライブラリなどを多く含む場合には、顕著にファイルサイズが大きくなってしまう。そこで、必要なファイルのみを必要なタイミングで読み込めるようにしようという考え方が、「コード分割(Code-Splitting)」である。webpack などを使って、細かい設定をすることが可能である。

React.lazy を使うことで、コンポーネントが呼び出されたタイミングで、そのコンポーネントに必要なコードだけを読み込むことができ、初回に読み込まないので、「遅延読み込み」と呼ばれている。

React.Suspense とは

React.lazyコンポーネント(遅延コンポーネント)は、Suspense コンポーネント内で必ずレンダーされる必要がある。また、Suspensefallback 属性に、待機中のコンテンツ(ローディングインジケータなど)を指定することで、遅延コンポーネントを読み込んでいる間に表示することができる。

<Suspense fallback={<div>Loading...</div>}>
  <LazyComponent1 />
  <LazyComponent2 />
</Suspense>

また、単一の Suspense コンポーネントで複数の遅延コンポーネントをラップすることができる。

ただし、React.lazySuspense はまだサーバーサイドレンダリングには使用できないので注意してください。

React.lazy + React.Suspense + React Router の実装

React RouterReact.lazyReact.Suspense を組み合わせることで、以下のメリットがある。

  • ページごとのコード分割ができ、パフォーマンスを向上させることができる
  • ローディング画面を画面遷移時に挿入できる

React Router に必要なモジュールをインストールする。

$ yarn add react-router react-router-dom

実装に関しては、Route のcomponent属性に、遅延コンポーネントを適応させ、Suspense でラップしてあげる。また、遅延コンポーネントは必ず export default でエクスポートするようにしておく。参考となるコードを以下に、記載しておく。

import React, { Suspense, lazy } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
const Home = lazy(() => import('./components/pages/Home'))
const Contact = lazy(() => import('./components/pages/Contact'))
const News = lazy(() => import('./components/pages/News'))
import Loading from './components/Loading'
import NoMatch from './components/pages/NoMatch'
const App = () => (
  <BrowserRouter>
    <Suspense fallback={Loading}>
      <Switch>
        <Route path="/" exact component={Home} />
        <Route path="/contact" component={Contact} />
        <Route path="/news" component={News} />
        <Route component={NoMatch} />
      </Switch>
    </Suspense>
  </BrowserRouter>
)
ReactDOM.render(<App />, document.querySelector('#root'))
const Home = () => (
  <div>Home</div>
)
export default Home
const Loading = () => (
  <div>Loading...</div>
)
export default Loading

本実装をすることによって、SPA で全ページの JavaScript ファイルを最初に一気に読み込む必要がなくなり、初期ロード時間を短縮することができる。ページごとにコードを分割して、非同期で読み込むことができる。以下のような流れになる。

  1. ビルド時に、コード分割が行われ、ページごとにjsファイルが分割される
  2. クライアントが、任意のページにアクセスする
  3. 任意のページに必要なコンポーネントのみが非同期で読み込まれる
  4. 読み込み中は、Suspense の fallback に指定したコンポーネントが表示される
  5. 読み込みが完了すると、任意のページが表示される

とても簡単に、ページごとのコード分割が実装でき、Loading画面も挿入できるので、非常に便利であると思えた。

まとめ

今回は、React.lazy + React.Suspense という機能を初めて触ってみた。React Router との相性も非常によく、今後の実装でも非常に使いやすいと思えた。

それでは、ステキな開発ライフを。

関連資料

ja.reactjs.org

reactrouter.com