庞大的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 才知道在它们加载期间应该显示什么内容。

如何将 SuspenseReact.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: falseloading 选项了。此时应该使用 Suspense 的 fallback 属性来替代它们。

React.lazynext/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

  • 如果你正在构建一个纯 React 应用程序(不使用 Next.js)

  • 如果你使用的是 Create React App、Vite 或自定义的 Webpack 配置

  • 如果你不需要服务器端渲染功能

  • 如果你希望采用一种与特定框架无关的简单解决方案

何时使用next/dynamic

  • 你正在构建一个Next.js应用

  • 某些组件需要服务器端渲染,而其他组件则不需要

  • 你希望使用内置的加载占位符,而无需手动添加Suspense

  • 你希望享受Next.js特有的优化功能及默认设置

实际应用案例

示例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。将这两种技术与及错误处理机制结合使用,就可以构建出一个完善的延迟加载系统。

首先识别出那些占用资源最多的组件(如图表、模态框或管理面板),然后对这些组件启用延迟加载功能。在实施延迟加载前后,分别测量应用程序的打包大小以及核心网页性能指标,从而了解这一改变所带来的实际效果。

Comments are closed.