龐大的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.