環境 - react@18.1.0 - vite@2.9.9 - typescript@4.6.4
React.lazy
Reacy.lazy
を使うと import()
で読み込んだコンポーネントを通常のコンポーネントとして扱うことができる
下記の方法でコンポーネントを Dynamic import できる
// component export default MyComponent: FC = () => {...}; // MyApp.tsx const MyComponent = React.lazy(() => import('./path/to/MyComponent')); // `<MyComponent />` として使用できる
React.lazy で Dynamic import したコンポーネントは <Suspense>
で囲う必要がある
遅延コンポーネントは、Suspense コンポーネント内でレンダーされる必要があります。これによって、遅延コンポーネントのローディングの待機中にフォールバック用のコンテンツ(ローディングインジケータなど)を表示できます。
cf. コード分割 – React
React.Susponse で囲ってないとエラーで React がクラッシュする
import 中にコンポーネントを表示しようとしてしまった場合エラーでアプリケーションがクラッシュします。動的な import なので常にクラッシュする訳ではないので気づきづらいので注意が必要です。
// MyComponent.tsx export default MyComponent: FC = () => {...}; // MyApp.tsx import { FC, lazy } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); const MyApp:FC = () => { return <MyComponent /> }
次のようなエラーが発生する
The above error occurred in one of your React components
Consider adding an error boundary to your tree to customize error handling behavior. Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.
👇 React.lazy で読み込んだコンポーネントは必ず <Suspense>
で囲いインポート中の fallback を設定する
// MyApp.tsx import { FC, lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); const MyApp:FC = () => { return ( <Suspense fallback="loading…"> <MyComponent /> </Suspense> ); }
named export されたコンポーネントを読み込む方法
デフォルトでは React.lazy
, import()
を使った Dynamic import はデフォルトでは default import
されたコンポーネントにしか対応されていません。
React.lazy は現在デフォルトエクスポートのみサポートしています。インポートしたいモジュールが名前付きエクスポートを使用している場合、それをデフォルトとして再エクスポートする中間モジュールを作成できます。これにより、tree shaking が機能し未使用のコンポーネントを取り込まず済むようにできます。
cf. コード分割 – React
1. default import する中間モジュールを別途作成する方法
公式に書かれている方法。named export されているコンポーネントを読み込み default import する中間もモジュールを作成し、それを Dynamic import する
Dynamic import したいコンポーネント
// ManyComponents.js export const MyComponent = /* ... */; export const MyUnusedComponent = /* ... */;
中間モジュール
// MyComponent.js export { MyComponent as default } from "./ManyComponents.js";
中間モジュールを React.lazy
で Dynamic import する
import { lazy } from 'react'; const MyComponent = lazy(() => import("./MyComponent.js"));
default import したオブジェクトの中から named export されたモジュールを default として返す
React.lazy
の型はこんな風になっていました。
type LazyExoticComponent<T extends ComponentType<any>> = ExoticComponent<ComponentPropsWithRef<T>> & { readonly _result: T; }; function lazy<T extends ComponentType<any>>( factory: () => Promise<{ default: T }> ): LazyExoticComponent<T>;
最終的に {default: ComponentType}
の形が返却されれば React.lazy
としては正確に動作しそうです。
React のコンポーネントは names export の場合も ESModule で export されていると思うので、全体を default インポートした Object には named export されたモジュールが含まれアクセスが可能なので、これを利用して default
キーで必要なモジュール返却すれば中間モジュールを作成しなくても Dynami import が可能でした。
Dynamic import したいコンポーネント
// ManyComponents.js export const MyComponent = /* ... */; export const MyUnusedComponent = /* ... */;
React.lazy
で Dynamic import
import { lazy } from 'react'; const MyComponent = lazy(() => import("./MyComponent").then(({ MyComponent }) => ({ default: MyComponent, })) );
問題としては複数のモジュールをexport しているファイルだと一度全体を import してしまうので Tree shaking が効かなくなることかと思います。単体のモジュールしか export していない場合であれば中間モジュールのファイルを作成せず React.lazy
で Dynamic import する方法として使えそうです。
[参考]