有兴趣阅读我们的下一本书吗?了解更多关于 使用 React 构建大型 JavaScript Web 应用

性能模式

动态导入

在我们的聊天应用程序中,我们有四个关键组件:UserInfoChatListChatInputEmojiPicker。然而,只有三个组件在初始页面加载时立即使用:UserInfoChatListChatInputEmojiPicker 不直接可见,甚至可能根本不会渲染,除非用户点击Emoji 来切换 EmojiPicker。这意味着我们不必要地将 EmojiPicker 模块添加到了初始捆绑包中,这可能会增加加载时间!

为了解决这个问题,我们可以动态导入 EmojiPicker 组件。我们不会静态导入它,而是在需要显示 EmojiPicker 时才导入它。在 React 中动态导入组件的一个简单方法是使用 React SuspenseReact.Suspense 组件接收需要动态加载的组件,这使得 App 组件可以通过挂起 EmojiPicker 模块的导入来更快地渲染其内容!当用户点击表情符号时,EmojiPicker 组件第一次被渲染。EmojiPicker 组件渲染一个 Suspense 组件,它接收懒加载的模块:在本例中为 EmojiPickerSuspense 组件接受一个 fallback 属性,它接收在挂起组件仍在加载时应该渲染的组件!

我们无需将 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 的导入,我们已将其减小到 1.33 MiB

在控制台中,你可以看到 EmojiPicker 直到我们切换 EmojiPicker 才会执行!

ChatInput.js
1import React, { Suspense, lazy } from "react";
2 // import Send from "./icons/Send";
3 // import Emoji from "./icons/Emoji";
4 const Send = lazy(() =>
5 import(/*webpackChunkName: "send-icon" */ "./icons/Send")
6 );
7 const Emoji = lazy(() =>
8 import(/*webpackChunkName: "emoji-icon" */ "./icons/Emoji")
9 );
10 // Lazy load EmojiPicker when <EmojiPicker /> renders
11 const Picker = lazy(() =>
12 import(/*webpackChunkName: "emoji-picker" */ "./EmojiPicker")
13 );
14
15 const ChatInput = () => {
16 const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);
17
18 return (
19 <Suspense fallback={<p id="loading">Loading...</p>}>
20 <div className="chat-input-container">
21 <input type="text" placeholder="Type a message..." />
22 <Emoji onClick={togglePicker} />
23 {pickerOpen && <Picker />}
24 <Send />
25 </div>
26 </Suspense>
27 );
28 };
29
30 console.log("ChatInput loaded", Date.now());
31
32 export default ChatInput;

构建应用程序时,我们可以看到 Webpack 创建的不同捆绑包。

通过动态导入 EmojiPicker 组件,我们成功地将初始捆绑包大小从 1.5MiB 减小到 1.33 MiB!虽然用户可能仍然需要等待一段时间,直到 EmojiPicker 完全加载,但我们通过确保应用程序在用户等待组件加载时能够渲染并交互,改善了用户体验。


可加载组件

服务器端渲染不支持 React Suspense(暂时)。loadable-components 库是 React Suspense 在 SSR 应用程序中的一个很好的替代方案,它可以在 SSR 应用程序中使用。

ChatInput.js
1import React from "react";
2import loadable from "@loadable/component";
3
4import Send from "./icons/Send";
5import Emoji from "./icons/Emoji";
6
7const EmojiPicker = loadable(() => import("./EmojiPicker"), {
8 fallback: <div id="loading">Loading...</div>
9});
10
11const ChatInput = () => {
12 const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);
13
14 return (
15 <div className="chat-input-container">
16 <input type="text" placeholder="Type a message..." />
17 <Emoji onClick={togglePicker} />
18 {pickerOpen && <EmojiPicker />}
19 <Send />
20 </div>
21 );
22};
23
24export default ChatInput;

与 React Suspense 类似,我们可以将懒加载的模块传递给 loadable,它只会在请求 EmojiPicker 模块时才导入该模块!在加载模块期间,我们可以渲染一个 fallback 组件。

虽然可加载组件是 SSR 应用程序中 React Suspense 的一个很好的替代方案,但它们在 CSR 应用程序中也非常有用,可以用于挂起模块的导入。

ChatInput.js
1import React from "react";
2 import Send from "./icons/Send";
3 import Emoji from "./icons/Emoji";
4 import loadable from "@loadable/component";
5
6 const EmojiPicker = loadable(() => import("./components/EmojiPicker"), {
7 fallback: <p id="loading">Loading...</p>
8 });
9
10 const ChatInput = () => {
11 const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);
12
13 return (
14 <div className="chat-input-container">
15 <input type="text" placeholder="Type a message..." />
16 <Emoji onClick={togglePicker} />
17 {pickerOpen && <EmojiPicker />}
18 <Send />
19 </div>
20 );
21 };
22
23 console.log("ChatInput loaded", Date.now());
24
25 export default ChatInput;