【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
コンポーネント内で必ずレンダーされる必要がある。また、Suspense
の fallback
属性に、待機中のコンテンツ(ローディングインジケータなど)を指定することで、遅延コンポーネントを読み込んでいる間に表示することができる。
<Suspense fallback={<div>Loading...</div>}> <LazyComponent1 /> <LazyComponent2 /> </Suspense>
また、単一の Suspense
コンポーネントで複数の遅延コンポーネントをラップすることができる。
ただし、React.lazy
と Suspense
はまだサーバーサイドレンダリングには使用できないので注意してください。
React.lazy + React.Suspense + React Router の実装
React Router
は React.lazy
と React.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 ファイルを最初に一気に読み込む必要がなくなり、初期ロード時間を短縮することができる。ページごとにコードを分割して、非同期で読み込むことができる。以下のような流れになる。
- ビルド時に、コード分割が行われ、ページごとにjsファイルが分割される
- クライアントが、任意のページにアクセスする
- 任意のページに必要なコンポーネントのみが非同期で読み込まれる
- 読み込み中は、Suspense の fallback に指定したコンポーネントが表示される
- 読み込みが完了すると、任意のページが表示される
とても簡単に、ページごとのコード分割が実装でき、Loading画面も挿入できるので、非常に便利であると思えた。
まとめ
今回は、React.lazy + React.Suspense という機能を初めて触ってみた。React Router との相性も非常によく、今後の実装でも非常に使いやすいと思えた。
それでは、ステキな開発ライフを。