Loading... *阅读时间约 11 分钟。* 本文发布于微信公众号[**码上花甲**](https://mp.weixin.qq.com/s?__biz=MzIzMDM4MzE3Mw==&mid=2247485582&idx=1&sn=0aff87b546717d47d628d9a48f3c81c2&chksm=e8b503a4dfc28ab2e87ce67c621c9223a1ff76cb1ba7101a8a665c8c28720f5d10072249b57d#rd),每天 9: 00 准时为你推送高质量前端开发相关文章!更有精品资源供你免费获取!欢迎关注。 --- 我们开发的 React 应用随着规模越来越大,如果还是将所有组件打包到一个 bundle 文件中,就可能会导致应用程序的加载时间变慢。常见的做法是,将程序中的组件打包到单独的文件中以减少应用的加载时间,通常我们会通过“按需加载”的方式来实现这一目的。在本文中,你将学会在 React 程序中关于组件动态导入的相关知识。 ## 1. **什么是动态导入组件?** 动态导入组件是指在需要时才加载组件,而不是在应用程序加载时将所有组件都打包到一个 bundle 文件中。在 React 中,我们该如何实现呢? React 为我们提供了一个 `lazy()` 方法和 `Suspense` 组件,这是实现动态导入组件的两个主要工具。 使用 `React.lazy()`,我们可以轻松地实现按需加载组件,它的语法如下: ```jsx const MyComponent = React.lazy(() => import('./MyComponent')); ``` 在上面的代码中,`React.lazy()` 接收一个函数,该函数返回一个 `import()` 函数调用。`import()` 函数是 ECMAScript 动态导入语法的一部分,它允许我们在运行时异步加载一个模块。通过将 `React.lazy()` 和 `import()` 函数结合使用,我们便可以按需加载组件。 当我们使用 `React.lazy()` 时,React 会自动将返回的组件包装在一个 `lazy` 组件中。因此,我们需要使用 `Suspense` 组件来渲染这个 `lazy` 组件。`Suspense` 组件允许我们在组件加载完成之前显示一个 loading 界面。下面是一个使用 `React.lazy()` 和 `Suspense` 组件的简单例子: ```jsx import React, { lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); function App() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> </div> ); } ``` 在上面的代码中,当 `<MyComponent />` 组件被渲染时,React 将自动异步加载 `MyComponent` 模块。`fallback` 属性指定了当组件加载时显示的 loading 界面。当组件加载完成时,React 将自动将其呈现。 ## 2. 异步列表组件示例 下面我们通过一个「异步列表」组件示例来演示 React.lazy() 和 Suspense,先看最终的效果:  ### 2.1 父组件 ArtistPage 我们通过一个父组件 ArtistPage 和一个子组件 Albums 来实现上面 gif 中的效果。父组件 ArtistPage 实现思路如下: 1. 页面加载完成后,默认渲染「Open The Beatles artist page」按钮,如果按钮被点击则展示列表数据; 2. 在展示列表数据前,首先通过 Suspense 展示一个过渡用的 Loading 页面,当子组件和其中的数据请求完成后再展示列表数据。 父组件 ArtistPage 代码如下: ```jsx import { Suspense, useState } from "react"; import Albums from "@/components/Albums"; export default function ArtistPage() { const [show, setShow] = useState(false); // 定义组件的状态 show,控制组件的渲染 // 初始时设置为 false,点击按钮后设置为 true // 以展示 The Beatles 的专辑页面 if (show) { // 如果 show 为 true,则渲染以下内容 return ( <> <h1>The Beatles</h1> {/* 使用 React Suspense 包装组件,添加 fallback UI */} <Suspense fallback={<Loading />}> {/* 传入 artistId 属性,渲染 The Beatles 的专辑 */} <Albums artistId="the-beatles" /> </Suspense> </> ); } else { // 如果 show 为 false,则渲染以下按钮 return ( <button onClick={() => setShow(true)}> Open The Beatles artist page </button> ); } } // 定义 Loading 组件,用于在数据加载时显示 function Loading() { return <h2>🌀 Loading...</h2>; } ``` 当用户点击「Open The Beatles artist page」按钮时,组件的状态会更新,页面上会渲染出 The Beatles 的列表。在这个页面中,通过 React Suspense 和组件 Albums 来展示 The Beatles 的列表数据。Suspense 组件可以处理异步加载数据时的等待状态,并展示 fallback 属性指定的组件。在上面的父组件 ArtistPage 中,fallback 展示了一个 Loading 组件,用来展示数据加载过程中的等待状态。 ### 2.2 子组件 Albums 我们将根据上面父组件 ArtistPage 传入的参数 articleId 字段来渲染子组件数据列表。示例代码如下: ```jsx import { fetchData } from "./data"; // 注意:这个组件使用的是一个实验性的 API,在 React 的稳定版本中尚未可用。 // 如果你想要一个实际的示例,可以尝试使用与 Suspense 集成的框架,例如 Relay 或 Next.js。 // Albums 组件,接收一个字符串类型的 artistId 参数 export default function Albums({ artistId }) { // 通过fetchData函数获取artistId下的专辑数据,使用use函数包装 const albums = use(fetchData(`/${artistId}/albums`)); return ( // 渲染专辑列表 <ul> {albums.map((album) => ( <li key={album.id}> {album.title} ({album.year}) </li> ))} </ul> ); } // 这是一个临时函数,用于让这个演示程序正常运行。 // TODO:用真正的实现替换此函数。 function use(promise) { // 如果 promise 状态已经是已完成,则返回结果 if (promise.status === "fulfilled") { return promise.value; } // 如果 promise 状态是拒绝,则抛出异常 else if (promise.status === "rejected") { throw promise.reason; } // 如果 promise 状态是挂起,则抛出异常 else if (promise.status === "pending") { throw promise; } // 如果 promise 状态未知,则先设置状态为挂起,然后等待 promise 结果 else { promise.status = "pending"; promise.then( (result) => { promise.status = "fulfilled"; promise.value = result; }, (reason) => { promise.status = "rejected"; promise.reason = reason; } ); throw promise; } } ``` 上面代码中的 use 函数是用来模拟请求异步数据用的,模拟展示获取数据的过程。如果你有 Express 或 Koa 的基础,可以在本地搭建一个简易的后端服务,将 use 函数的逻辑替换成真实的异步请求即可。这里的 use 方法会将数据获取到,然后把返回的数据渲染到一个无序列表中。在这一过程中,Suspense 会在数据未加载完成时展示父组件的 fallback 属性中的组件。fallback 中的组件是 Loading 组件。 最终的效果便是 gif 展示的样子。  ## 3. 总结 动态导入组件是一种优化 React 应用程序性能的有效方法,可以让我们根据需要按需加载组件,而不是在应用程序加载时将所有组件都打包到一个文件中。使用 `React.lazy()` 和 `Suspense` 组件,我们可以很轻松地实现动态导入组件。不过,虽然动态导入组件可以提高我们程序的性能,但还是有一些事项需要注意: * `React.lazy()` 只适用于默认导出组件。如果要按需加载命名导出的组件,你需要使用普通的动态导入语法。 * 由于动态导入组件需要进行网络请求,因此可能会对性能产生一定影响。因此,最好将动态导入组件与代码分割技术一起使用,以便只加载需要的代码。 * 在使用 `React.lazy()` 时,我们必须要使用 `Suspense` 组件。如果没有使用 `Suspense` 组件,则可能会出现渲染问题。 * 在某些情况下,`React.lazy()` 可能无法正常工作。例如,如果要动态导入的组件位于 `node_modules` 目录中,则可能无法正常工作。此时,我们需要使用其他的按需加载组件的方法,例如使用第三方库 `loadable-components`。 * 如果项目中使用了 Webpack 4 或更高版本,那么我们可以结合 `webpackChunkName` 使用 `React.lazy()`,这样可以为动态加载的代码块提供更有意义的名称。比如: ```jsx const MyComponent = lazy( () => import(/* webpackChunkName: "my-component" */ "./MyComponent") ); ``` 在上面的代码中,我们为导入的代码块提供了一个名为 `"my-component"` 的名称。这个名称是生成的代码块的名称,以便我们更容易地识别和调试。 相信通过上面的示例你应该了解了 React.lazy() 和 Suspense,熟练掌握它们是 React 开发中进阶中高级所必须得。如果对你有所启发,记得点个关注哦~  Last modification:April 21, 2023 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 1 如果觉得我的文章对你有用,请随意赞赏
One comment