← 返回蜂巢洞察 庞大的JavaScript代码包会降低应用程序的运行速度。当大量代码同时被加载时,用户需要等待更长时间才能看到页面内容,而且页面的反应速度也会变慢。搜索引擎也可能会将这类网站的排名降得较低。
懒加载技术通过将代码分割成较小的部分,并仅在真正需要时才进行加载,从而帮助解决这一问题。
本指南将为您详细介绍如何在React和Next.js中使用懒加载功能。阅读完本指南后,您将了解何时该使用`React.lazy`、`next/dynamic`以及`Suspense`,同时还会获得一些可供您复制并应用于自己项目的实际示例。
目录
什么是懒加载?
懒加载是一种性能优化技术,它会在代码真正被需要时才进行加载。与其一次性加载整个应用程序,不如将其分割成较小的部分——只有当用户访问某个特定页面或使用某项功能时,浏览器才会下载相应的代码片段。
懒加载带来的好处包括:
-
更快的初始加载速度:由于代码被分成了小块,因此应用程序的启动速度会更快。
-
更好的核心Web性能指标:懒加载有助于提升“最大内容绘制时间”和“总阻塞时间”等关键指标。
-
更低的带宽消耗:用户只需下载实际需要的代码,从而节省了网络资源。
在React中,您可以通过动态导入功能以及`React.lazy()`来实现懒加载;而在Next.js中,则可以使用`next/dynamic`来完成同样的任务。
先决条件
在开始学习之前,请确保您已经具备以下条件:
-
对React有基本的了解,包括组件、钩子以及状态管理等概念。
-
已安装Node.js(建议使用18版或更高版本)。
-
拥有一个React应用程序(可以使用Create React App或Vite创建),或者一个Next.js应用程序(用于后续的示例学习)。
对于React相关的示例,您可以选择使用Create React App或Vite;而对于Next.js的示例,则需要使用App Router功能(Next.js 13版及以上版本支持)。
如何使用`React.lazy`进行代码分割
React.lazy()允许你将某个组件定义为动态导入的组件。React只会在该组件首次被渲染时才会加载它。React.lazy() 需要一个能够返回动态调用的 import() 函数。被导入的模块必须使用默认导出方式。
下面是一个基本的示例:
import { lazy } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminDashboard = lazy(() => import('./Admin Dashboard');
function App() {
return (
);
}
如果使用带名称的导出,你可以将它们映射为默认导出:
const ComponentWithNamedExport = lazy(() => {
import('./MyComponent').then((module) => ({
default: module NamedComponent,
}));
});
你还可以为这些代码块指定名称,以便在浏览器中进行更便捷的调试:
const HeavyChart = lazy(() => {
import(/* webpackChunkName: "heavy-chart" */ './HeavyChart');
});
单凭 React.lazy() 是不够的。你必须将这些懒加载组件包裹在 Suspense 中,这样 React 才知道在它们加载期间应该显示什么内容。
如何将 Suspense 与 React.lazy 一起使用
Suspense 是一个 React 组件,当它的子组件正在加载时,它会显示替代内容。它与 React.lazy() 结合使用,可以处理动态导入组件的加载状态。
将你的懒加载组件包裹在 Suspense 中,并提供一个 fallback 属性:
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminDashboard = lazy(() => import('./Admin Dashboard');
function App() {
return (
我的应用
正在加载图表...
正在加载管理面板...
);
}
你也可以为多个懒加载组件使用同一个 Suspense 结构:
正在加载中...
一个设计得更精良的替代内容显示方式,能够提升用户体验:
function LoadingSpinner() {
return (
);
}
}>
如何使用错误边界来处理错误
React.lazy() 和 Suspense 并不能处理加载错误(例如网络故障或某些代码块缺失)。为此,你需要使用错误边界。
“错误边界”是一种组件,它使用`componentDidCatch`或`static getDerivedStateFromError`来捕获其子树中出现的错误,并显示替代界面。
下面是一个简单的错误边界示例:
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('错误被错误边界捕获:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || 发生了错误。
;
}
return this.props.children;
}
}
请将你的`Suspense`组件包裹在错误边界中:
import { lazy, Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';
const HeavyChart = lazy(() => import('./HeavyChart');
function App() {
return (
图表加载失败,请重试。
正在加载图表……
);
}
如果某个组件无法成功加载,错误边界会捕获这个错误,并显示替代界面,而不会让页面显示空白内容或出现未处理的错误。
如何在Next.js中使用`next/dynamic`
Next.js提供了`next/dynamic`这个功能,它结合了`React.lazy()`和`Suspense`,并为Next.js添加了一些专门的设计选项(包括服务器端渲染功能)。
基本用法如下:
'use client';
import dynamic from 'next/dynamic';
const ComponentA = dynamic(() => import('../components/A'));
const ComponentB = dynamic(() => import('../components/B');
export default function Page() {
return (
);
}
自定义加载界面
你可以通过设置`loading`选项,在组件加载期间显示占位符:
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
loading: () => 正在加载图表……
,
});
禁用服务器端渲染
对于那些只能在客户端运行的组件(例如那些使用`window`或仅浏览器可用API的组件),请将`ssr: false`设置为这些组件的属性:
const ClientOnlyMap = dynamic(() => import('../components/Map'), {
ssr: false,
loading: () => 正在加载地图……
,
});
注意:`ssr: false`仅适用于客户端组件。请在包含`’use client’`语句的文件中使用这个选项。
按需加载组件
只有当满足特定条件时,你才能加载某个组件:
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
const Modal = dynamic(() => import('../components/Modal'), {
loading: () => 正在加载模态框...
,
});
export default function Page() {
const [showModal, setShowModal] = useState(false);
return (
{showModal && }
);
}
命名导出
对于需要命名导出的情况,你需要通过动态导入来获取相应的组件:
const Hello = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
);
结合 next/dynamic 使用 Suspense
在 React 18 及更高版本中,你可以使用 suspense: true,这样就可以依赖父组件的 Suspense 结构来处理加载逻辑,而无需使用 loading 选项:
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
suspense: true,
});
// 在你的组件中:
正在加载中...}>
重要提示:当使用 suspense: true 时,就不能再使用 ssr: false 或 loading 选项了。此时应该使用 Suspense 的 fallback 属性来替代它们。
React.lazy 与 next/dynamic:何时使用哪种方式
| 功能 |
React.lazy + Suspense |
next/dynamic |
| 适用框架 |
任何 React 应用程序(包括 Create React App、Vite 等) |
仅适用于 Next.js |
| 服务器端渲染 |
不支持 |
默认支持 |
| 禁用服务器端渲染 |
不适用 |
可以通过 ssr: false 来禁用 |
| 加载提示界面 |
需要使用 Suspense 的 fallback 属性 |
内置了 loading 选项 |
| 错误处理 |
需要设置 Error Boundary |
同样需要设置 Error Boundary |
| 命名导出功能 |
需要手动使用 .then() 来处理导入结果 |
同样需要使用 .then() 来处理导入结果 |
| Suspense 的使用方式 |
总是会启用 Suspense 功能 |
可以通过 suspense: true 来选择是否启用 |
何时使用 React.lazy
何时使用next/dynamic
实际应用案例
示例1:基于路由的代码分割技术在React中的应用
通过路由来划分应用程序的结构,这样只有当用户访问相应页面时,该页面才会被加载:
// App.jsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard');
const Settings = lazy(() => import('./pages/Settings');
function App() {
return (
页面加载失败。>
div>>正在加载中...>
} />
} />>
} />>
);
}
示例2:在Next.js中延迟加载复杂的图表库
只有当用户打开分析页面时,才加载该图表库:
// app/analytics/page.jsx
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('../components/Chart'), {
ssr: false,
loading: () => (
),
});
export default function AnalyticsPage() {
const [showChart, setShowChart] = useState(false);
return (
分析页面
{showChart && }
);
}
示例3:延迟加载模态框组件
只有当用户点击打开模态框时,才将其加载出来:
// React (使用React.lazy)
import { lazy, Suspense, useState } from 'react';
const Modal = lazy(() => import('./Modal');
function ProductPage() {
const [showModal, setShowModal] = useState(false);
return (
{showModal && (
)}
);
}
// Next.js(使用next/dynamic)
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
const Modal = dynamic(() => import('./Modal'), {
loading: () => null,
});
export default function ProductPage() {
const [showModal, setShowModal] = useState(false);
return (
{showModal && setShowModal(false)} />}
);
}
示例4:延迟加载外部库
只有当用户真正需要某个库时,才去加载它(例如,当用户在搜索框中开始输入内容时):
handleSearch(e.target.value)}
/>
{results.map((result) => (
- {result.item}
))}
);
}
结论
延迟加载技术通过将代码分割成多个部分,并仅在需要时才加载这些部分,从而提升应用程序的性能。以下是你所学到的内容:
-
React.lazy()——在普通的React项目中使用,用于实现代码的分割。该函数要求被导入的组件具有默认导出功能,并且可以与动态导入语句import()配合使用。
-
Suspense——将需要延迟加载的组件包裹在标签中,同时为加载过程中可能出现的错误状态提供相应的处理方式。
-
错误处理机制——利用这些机制可以捕获代码分块加载时可能出现的错误,并向用户展示友好的错误提示界面。
-
next/dynamic——在Next.js项目中使用,除了具备上述优势外,还支持服务器端渲染以及内置的加载选项。
对于仅使用React的项目,可以选择React.lazy;而对于Next.js项目,则应选择next/dynamic。将这两种技术与及错误处理机制结合使用,就可以构建出一个完善的延迟加载系统。
首先识别出那些占用资源最多的组件(如图表、模态框或管理面板),然后对这些组件启用延迟加载功能。在实施延迟加载前后,分别测量应用程序的打包大小以及核心网页性能指标,从而了解这一改变所带来的实际效果。
相关文章
技术实践
当人们提到杰弗里·辛顿时,他们通常会想到反向传播算法、玻尔兹曼机、深度信念网络,以及那些彻底改变了人工智能发展的深度学习技术。 然而,很少有人会追溯到他科研生涯的起点。 1977年,在那篇著名的关于反向传播的论文发表之前的近十年时间里,辛顿在爱丁堡大学完成了他的博士论文,题为《松弛机制在视觉认知中的作用》。乍一看,这篇论文似乎只是探讨计算机视觉与松弛算法之间的关系。当我开始阅读它时,我也正是抱着这样的预期。 然而,在仔细研读之后,我意识到这篇论文涉及的远不止视觉算法。许多后来成为辛顿研究核心的理念,在当时就已经初具雏形。虽然当时的术语有所不同,数学表达也更为简单,神经网络也尚未成为他研究的重点
阅读全文
技术实践
大多数工程团队最初并不是为了管理基础设施而开始工作的。他们通常是带着某个产品创意 … Read More
阅读全文
技术实践
外部数据封装机制使得远程的Postgres表看起来就像本地的表一样。正是这种便利 … Read More
阅读全文
技术实践
在当今快速的数字环境中,软件程序在我们的日常生活中发挥着至关重要的作用。从移动应 … Read More
阅读全文