30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

React.Suspense と React.lazy でコンポーネントを動的に読み込む

React.SuspenseReact.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の値が表示される。

それを確認するため、OneTwoを時間差で読み込ませる。

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が表示される。読み込みが終わったものから順次表示する、とはならない。

条件分岐で読み込むコンポーネントを変える

以下のコードでは、isOnetruthyのときに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;

参考資料