分かった気になるDynamic Import
はじめに
Dynamic Import は必要な時に、必要なファイルを読み込みます。
Dynamic Importの使用を検討する必要がある検討箇所を複数述べます。
-
static import
すると、コードの読み込みが大幅に遅くなり、インポートするコードが必要になる可能性が低くなるか、後で必要になる可能性が低くなります。 -
static import
すると、プログラムのメモリー使用量が大幅に増加し、インポートするコードが必要になる可能性が低くなります。 - 読み込み時に読み込むファイルが存在しないケース
- 読み込むファイルに副作用があり、ある条件が
true
ではない限り、その副作用を望まない場合。(ファイルに副作用がないことを推奨しますが、ファイルの依存関係でこれを制御できない場合があります。)
すべての場合において、Dynamic import を使用するのは賢い判断ではなく、Dynamic import は必要な場合にのみ使用してください。
初期の依存関係をロードするにはstatic importが適していて、tree shakingの恩恵をよりよく受けることができます。
ですが、この説明だけだと分かった気にはなれませんね。
具体的なケースを挙げてみましょう。
具体的に考えよう
今回のチャットアプリケーションでは、4つの重要なコンポーネントがあります。UserInfo
、ChatList
、ChatInput
、EmojiPicker
です。
しかし、これらのコンポーネントのうち、最初のページの読み込み時に即座に使用されるのは、3つだけでUserInfo
、ChatList
、ChatInput
です。
EmojiPicker
は直接表示されず、ユーザーが EmojiPicker
を切り替えるために Emoji をクリックしない場合は、まったく描画されない可能性もあります。
この場合、EmojiPickerファイル
を初期バンドルに追加することになり、読み込み時間が長くなる可能性を高くしてしまいました。
これを解決するために、EmojiPickerコンポーネント
を動的に読み込むことができます(Dynamic import)。
静的に読み込むのではなく、EmojiPickerを表示するときだけ読み込むのです。
CSR で Dynamic import
Reactでコンポーネントを動的に読み込む簡単な方法は、React Suspenseを使用することです。
React.Suspensionコンポーネントは、動的に読み込むべきコンポーネントを受け取り、EmojiPickerファイルの読み込みを一時停止することで、Appコンポーネントのコンテンツをより速く描画することをできます。
React.Suspensionコンポーネントは、EmojiPickerコンポーネント
の読み込みを一時停止します。
ユーザーが絵文字をクリックすると、EmojiPickerコンポーネント
が初めて描画される。
EmojiPickerコンポーネント
は Suspenseコンポーネント
を描画し、このコンポーネントが遅延インポートされたモジュール (この場合は EmojiPicker) を受け取ります。Suspense コンポーネントは、fallback prop を受け取り、suspended コンポーネントのロード中にレンダリングされるべきコンポーネントを受け取ります。
EmojiPicker を不要に初期バンドルに追加する代わりに、独自のバンドルに分割し、初期バンドルのサイズを小さくすることができます!
初期バンドルサイズが小さくなれば、初期ロードが速くなります。ユーザーは、長い間、空白のロード画面を見つめる必要がなくなります。
fallbackコンポーネント
は、アプリケーションがフリーズしていないことをユーザーに知らせます:ユーザーは、モジュールが処理され実行されるまで少し待つ必要があるだけです。
Asset Size Chunks Chunk Names
emoji-picker.bundle.js 1.48 KiB 1 [emitted] emoji-picker
main.bundle.js 1.33 MiB main [emitted] main
vendors~emoji-picker.bundle.js 171 KiB 2 [emitted] vendors~emoji-picker
従来は初期バンドルが1.5MiBだったのに対し、EmojiPickerの読み込みをSuspence(停止)することで、1.33MiBに減らすことができました。
コンソールでは、EmojiPickerを toggle するまで実行されないことが分かります
import React, { Suspense, lazy } from "react";
// import Send from "./icons/Send";
// import Emoji from "./icons/Emoji";
const Send = lazy(() =>
import(/*webpackChunkName: "send-icon" */ "./icons/Send")
);
const Emoji = lazy(() =>
import(/*webpackChunkName: "emoji-icon" */ "./icons/Emoji")
);
// Lazy load EmojiPicker when <EmojiPicker /> renders
const Picker = lazy(() =>
import(/*webpackChunkName: "emoji-picker" */ "./EmojiPicker")
);
const ChatInput = () => {
const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);
return (
<Suspense fallback={<p id="loading">Loading...</p>}>
<div className="chat-input-container">
<input type="text" placeholder="Type a message..." />
<Emoji onClick={togglePicker} />
{pickerOpen && <Picker />}
<Send />
</div>
</Suspense>
);
};
console.log("ChatInput loaded", Date.now());
export default ChatInput;
アプリケーションをビルドすると、Webpackが作成したさまざまなバンドルが表示されます。
EmojiPickerコンポーネント
を動的に読み込むことで、最初のバンドルサイズを 1.5MiB から 1.33MiB に減らすことに成功しました!
EmojiPicker
が完全に読み込まれるまで、ユーザーはまだしばらく待たなければなりませんが、ユーザーがコンポーネントの読み込みを待っている間、アプリケーションが描画され、インタラクティブになるようにすることで、UXを向上させることができました。
loadable components
SSR(サーバーサイドレンダリング)はまだReact Suspenseをサポートしていません。
React Suspenseに代わるものとして、loadable-componentsライブラリ
があり、SSR で使用できます。
import React from "react";
import loadable from "@loadable/component";
import Send from "./icons/Send";
import Emoji from "./icons/Emoji";
const EmojiPicker = loadable(() => import("./EmojiPicker"), {
fallback: <div id="loading">Loading...</div>
});
const ChatInput = () => {
const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);
return (
<div className="chat-input-container">
<input type="text" placeholder="Type a message..." />
<Emoji onClick={togglePicker} />
{pickerOpen && <EmojiPicker />}
<Send />
</div>
);
};
export default ChatInput;
React Suspense と同様に、lazy import
されたファイルを loadable に渡すことで、EmojiPickerファイル
がリクエストされたときだけ、ファイルを読み込みます。
ファイルが読み込みされている間、fallbackコンポーネントを描画することができます。
loadable コンポーネントは SSR で React Suspense に代わる素晴らしい方法ですが、CSR でもファイルの読み込みを一時停止するために有用そうですね。
import React from "react";
import Send from "./icons/Send";
import Emoji from "./icons/Emoji";
import loadable from "@loadable/component";
const EmojiPicker = loadable(() => import("./components/EmojiPicker"), {
fallback: <p id="loading">Loading...</p>
});
const ChatInput = () => {
const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);
return (
<div className="chat-input-container">
<input type="text" placeholder="Type a message..." />
<Emoji onClick={togglePicker} />
{pickerOpen && <EmojiPicker />}
<Send />
</div>
);
};
console.log("ChatInput loaded", Date.now());
export default ChatInput;
こちらの記事を参考にしております。(とても面白い記事ですので、ぜひ一読してみてください)
Discussion