动态导入

在聊天应用程序中,有四个主要部分:UserInfo,ChatList,ChatInput和EmojiPicker。

但是,在初始页面加载时,只有三个组件会立即使用:UserInfo、ChatList和ChatInput。

EmojiPicker不是直接可见的,用户甚至不会点击 EmojiPicker。
这意味着不必将 EmojiPicker 模块添加到初始包中,否则可能会增加加载时间!

为了解决这个问题,可以动态导入的 EmojiPicker 组件。


React Suspense

在 React 中动态导入组件的一种简单方法是使用 React Suspense

React.Suspense 组件接收应动态加载的部件,这使得它可以使 App 组件可以通过暂停进口更快的渲染它的内容 EmojiPicker 模块!

当用户单击表情符号时,EmojiPicker 组件将首次呈现。

EmojiPicker 组件渲染一个 Suspense 组件,该组件接收延迟导入的模块:Suspense 组件接受一个 fallback prop,它接收应该在挂起的组件仍在加载时渲染的组件!

而不是不必要地添加 EmojiPicker 到初始捆绑包,可以把它分成自己的包并减小初始包的大小!

较小的初始包大小意味着更快的初始加载:

用户不必长时间盯着空白的加载屏幕。
该 fallback 组件让用户知道应用程序还没有冻结:他们只需要等待一段时间来处理和执行模块。

1
2
3
4
5

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 的导入将其减少到 1.33 MiB!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

// 之前的 components/ChatInput.js
import React from "react";
import Send from "./icons/Send";
import Emoji from "./icons/Emoji";

import Picker from "./EmojiPicker";

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 && <Picker />}
      <Send />
    </div>
  );
};

// 现在的 components/ChatInput.js
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>
  );
};

在构建应用程序时,可以看到 Webpack 创建的不同包。

通过动态导入 EmojiPicker 组件,设法将初始包大小从 1.5MiB 减少到 1.33 MiB!

尽管用户可能仍然需要等待一段时间才能完全加载 EmojiPicker,但通过确保在用户等待组件加载时呈现应用程序和交互来改善用户体验。


loadable-components ✔

服务器端渲染不支持 React Suspense(目前)。

React Suspense 的一个很好的替代品是 loadable-components 库,它可以在 SSR 应用程序中使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

// components/ChatInput.js
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 类似,

可以将延迟导入的模块传递给 loadable,它只会在请求 EmojiPicker 模块时才导入该模块!

在加载模块时,可以渲染回退组件。

尽管可加载组件是用于 SSR 应用程序的 React Suspense 的绝佳替代品,它在 CSR 应用程序中也很有用,用以暂停模块的导入。


知识点

  • React.Suspense

  • fallback prop

  • loadable-components