性能模式
动态导入
在我们的聊天应用程序中,我们有四个关键组件:UserInfo
、ChatList
、ChatInput
和 EmojiPicker
。然而,只有三个组件在初始页面加载时立即使用:UserInfo
、ChatList
和 ChatInput
。EmojiPicker
不直接可见,甚至可能根本不会渲染,除非用户点击Emoji
来切换 EmojiPicker
。这意味着我们不必要地将 EmojiPicker
模块添加到了初始捆绑包中,这可能会增加加载时间!
为了解决这个问题,我们可以动态导入 EmojiPicker
组件。我们不会静态导入它,而是在需要显示 EmojiPicker
时才导入它。在 React 中动态导入组件的一个简单方法是使用 React Suspense。React.Suspense
组件接收需要动态加载的组件,这使得 App
组件可以通过挂起 EmojiPicker
模块的导入来更快地渲染其内容!当用户点击表情符号时,EmojiPicker
组件第一次被渲染。EmojiPicker
组件渲染一个 Suspense
组件,它接收懒加载的模块:在本例中为 EmojiPicker
。Suspense
组件接受一个 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
才会执行!
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 /> renders11 const Picker = lazy(() =>12 import(/*webpackChunkName: "emoji-picker" */ "./EmojiPicker")13 );1415 const ChatInput = () => {16 const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);1718 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 };2930 console.log("ChatInput loaded", Date.now());3132 export default ChatInput;
构建应用程序时,我们可以看到 Webpack 创建的不同捆绑包。
通过动态导入 EmojiPicker
组件,我们成功地将初始捆绑包大小从 1.5MiB
减小到 1.33 MiB
!虽然用户可能仍然需要等待一段时间,直到 EmojiPicker
完全加载,但我们通过确保应用程序在用户等待组件加载时能够渲染并交互,改善了用户体验。
可加载组件
服务器端渲染不支持 React Suspense(暂时)。loadable-components
库是 React Suspense 在 SSR 应用程序中的一个很好的替代方案,它可以在 SSR 应用程序中使用。
1import React from "react";2import loadable from "@loadable/component";34import Send from "./icons/Send";5import Emoji from "./icons/Emoji";67const EmojiPicker = loadable(() => import("./EmojiPicker"), {8 fallback: <div id="loading">Loading...</div>9});1011const ChatInput = () => {12 const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);1314 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};2324export default ChatInput;
与 React Suspense 类似,我们可以将懒加载的模块传递给 loadable
,它只会在请求 EmojiPicker
模块时才导入该模块!在加载模块期间,我们可以渲染一个 fallback
组件。
虽然可加载组件是 SSR 应用程序中 React Suspense 的一个很好的替代方案,但它们在 CSR 应用程序中也非常有用,可以用于挂起模块的导入。
1import React from "react";2 import Send from "./icons/Send";3 import Emoji from "./icons/Emoji";4 import loadable from "@loadable/component";56 const EmojiPicker = loadable(() => import("./components/EmojiPicker"), {7 fallback: <p id="loading">Loading...</p>8 });910 const ChatInput = () => {11 const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);1213 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 };2223 console.log("ChatInput loaded", Date.now());2425 export default ChatInput;