React.Suspense
とReact.lazy
を使うことで、import()
で動的に読み込んだコンポーネントを通常のコンポーネントとしてレンダリングすることができる。
動的読み込みはパフォーマンス向上のためなどに使われるが、それを簡単に React アプリに取り入れることができる。
import()
の概要はこちらを参照。
numb86-tech.hatenablog.com
この記事に出てくるコードは React のv16.10.2
で動作確認している。
React.lazy
React.lazy
は、コンポーネントを返す関数。引数には、import()
の返り値をそのまま返す関数を渡す。そしてimport()
で読み込まれるモジュールは、コンポーネントをdefault
でエクスポートしている必要がある。
そのため、以下のようになる。
// One.js import React from 'react'; const One = () => <span>1</span>; export default One; // App.js const One = React.lazy(() => import('./One'));
これで、One
を動的に読み込み、それでいて普通のコンポーネントと同じように使えるようになった。
React.Suspense
動的に読み込んだコンポーネントは、必ずSuspense
要素でラップされる必要がある。そしてSuspense
要素にはfallback
属性が必須である。
具体的には以下のようになる。
<Suspense fallback="loading...">
<One />
</Suspense>
One
が読み込まれるまではfallback
に渡された内容が表示され、読み込みが終わるとOne
の表示に切り替わる。
上記の例ではfallback
に文字列を渡しているが、React 要素なら何でもよい。
コード全体は以下のようになる。
// One.js import React from 'react'; const One = () => <span>1</span>; export default One; // App.js import React, {Suspense} from 'react'; const One = React.lazy(() => import('./One')); const App = () => { return ( <div> <Suspense fallback="loading..."> <One /> </Suspense> </div> ); }; export default App;
一瞬だけloading...
が表示されたあと、1
が表示される。
Suspense
には、動的に読み込んだコンポーネントを複数ラップできる。また、動的に読み込んだコンポーネント以外のコンポーネントも、ラップできる。
// Two.js import React from 'react'; const Two = () => <span>2</span>; export default Two; // App.js import React, {Suspense} from 'react'; const One = React.lazy(() => import('./One')); const Two = React.lazy(() => import('./Two')); const App = () => { return ( <div> <Suspense fallback="loading..."> <One /> <Two /> <span>3</span> </Suspense> </div> ); }; export default App;
動的に読み込んだコンポーネントを複数ラップした場合、全てのコンポーネントが読み込まれた時点で、ラップした内容を表示する。それまではfallback
の値が表示される。
それを確認するため、One
とTwo
を時間差で読み込ませる。
import React, {Suspense} from 'react'; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const One = React.lazy(() => sleep(1000).then(() => import('./One'))); const Two = React.lazy(() => sleep(2000).then(() => import('./Two'))); const App = () => { return ( <div> <Suspense fallback="loading..."> <One /> <Two /> <span>3</span> </Suspense> </div> ); }; export default App;
このようにすると、約2秒間loading...
が表示され、その後123
が表示される。読み込みが終わったものから順次表示する、とはならない。
条件分岐で読み込むコンポーネントを変える
以下のコードでは、isOne
がtruthy
のときにOne
が、falsy
のときにTwo
が、それぞれ読み込まれる。
該当しないほうのコンポーネントは、表示に使う使わないではなく、そもそも読み込まれない。
import React, {Suspense} from 'react'; const isOne = true; const TargetComponent = React.lazy(() => { if (isOne) return import('./One'); return import('./Two'); }); const App = () => { return ( <div> <Suspense fallback="loading..."> <TargetComponent /> </Suspense> </div> ); }; export default App;