使用 React.lazy 和 Suspense 进行代码拆分
永远不要向用户发送不必要的代码,所以请拆分您的代码包以确保不会发生这种情况!
通过 React.lazy
方法可以轻松地使用动态导入在组件级别对 React 应用程序进行代码拆分。
import React, { lazy } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const DetailsComponent = () => (
<div>
<AvatarComponent />
</div>
)
为什么这很有用? #
一个大型 React 应用程序通常由许多组件、实用方法和第三方库组成。如果不努力尝试仅在需要时才加载应用程序的不同部分,那么只要用户加载第一个页面,就会立即向用户发送一个很大的 JavaScript 代码包。这可能会大大影响页面性能。
React.lazy
函数提供了一个内置方法,可以将应用程序中的组件分离为单独的工作量很少的 JavaScript 块。然后将其与 Suspense
组件结合使用时,可以处理加载状态。
Suspense #
向用户发送较大 JavaScript 有效负载的问题在于页面完成加载所需的时间长度,尤其是在较弱的设备和网络连接上。这就是代码拆分和延迟加载非常有用的原因。
但是,通过网络获取代码拆分组件时,用户总是会遇到轻微延迟,因此显示有用的加载状态很重要。将 React.lazy
与 Suspense
组件一起使用有助于解决此问题。
import React, { lazy, Suspense } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const renderLoader = () => <p>Loading</p>;
const DetailsComponent = () => (
<Suspense fallback={renderLoader()}>
<AvatarComponent />
</Suspense>
)
Suspense
接受一个 fallback
组件,它允许您将任何 React 组件显示为一个加载状态。以下示例说明了工作原理。仅当点击按钮时,才会显示头像,点击时会发出请求以检索暂停的 AvatarComponent
所需的代码。同时,显示 fallback 加载组件。
这里,构成 AvatarComponent
的代码很少,所以加载旋转图标只显示了很短时间。加载较大的组件可能需要更长的时间,尤其是在网络连接较弱的情况下。
为了更好地演示工作原理:
To preview the site, press View App. Then press Fullscreen
. - Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
Click the Network tab.
点击 Throttling(限制)下拉列表,该下拉列表默认设置为 No throttling(无限制)。选择 Fast 3G(快速 3G)。
点击应用程序中的 Click Me(点击我)按钮。
加载指示器现在将显示更长时间。 注意构成 AvatarComponent
的所有代码是如何以单独块的形式获取的。
暂停多个组件 #
Suspense
的另一个功能是允许暂停加载多个组件,即使它们已经被延迟加载。
例如:
import React, { lazy, Suspense } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));
const renderLoader = () => <p>Loading</p>;
const DetailsComponent = () => (
<Suspense fallback={renderLoader()}>
<AvatarComponent />
<InfoComponent />
<MoreInfoComponent />
</Suspense>
)
这是一种非常有用的方法,可以在仅显示单个加载状态的情况下延迟多个组件的呈现。一旦所有组件都完成获取,用户就会看到它们一起同时显示。
可以通过以下嵌入程序看到这一效果:
如果不使用此功能,很容易遇到交错加载的问题,或者 UI 的不同部分一个接一个加载,每个部分都有自己的加载指示器。这会使用户体验更加痛苦。
处理加载失败 #
Suspense
允许在后台发出网络请求时显示暂时加载状态。但是如果这些网络请求由于某种原因失败,会发生什么情况?您可能处于离线状态,或者您的 Web 应用程序正在尝试延迟加载一个已过时并且在服务器重新部署后不再可用的版本化 URL。
React 有一个标准模式来优雅地处理这些类型的加载失败:使用错误边界。如文档中所述,任何 React 组件都可以作为错误边界,只要其实现了生命周期方法 static getDerivedStateFromError()
和 componentDidCatch()
中的一个或两个均实现。
要检测和处理延迟加载失败,您可以包装您的 Suspense
组件,其中将父组件用作错误边界。在错误边界的 render()
方法中,如果没有错误,则可以按原样呈现子组件,如果出现问题,则呈现自定义错误消息:
import React, { lazy, Suspense } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));
const renderLoader = () => <p>Loading</p>;
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {hasError: false};
}
static getDerivedStateFromError(error) {
return {hasError: true};
}
render() {
if (this.state.hasError) {
return <p>Loading failed! Please reload.</p>;
}
return this.props.children;
}
}
const DetailsComponent = () => (
<ErrorBoundary>
<Suspense fallback={renderLoader()}>
<AvatarComponent />
<InfoComponent />
<MoreInfoComponent />
</Suspense>
</ErrorBoundary>
)
结论 #
如果您不确定从哪里开始对 React 应用程序进行代码拆分,请按照以下步骤操作:
- 从路由级别开始。路由是识别应用程序拆分点的最简单方法。React 文档展示了如何将
Suspense
与react-router
一起使用。 - 确定网站页面上仅在特定用户交互(例如点击按钮)时呈现的大型组件。拆分这些组件将最大限度地减少 JavaScript 有效负载。
- 考虑拆分不在屏幕内并且对用户不重要的任何其他组件。