React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React中Suspense和lazy懒加载

React中Suspense和lazy实现懒加载的几种场景

作者:JJCTO袁龙

本文用5大高频场景和万能决策模板,教你用React.lazy和Suspense实现路由、弹窗、图表等组件的按需加载,配合错误处理和预加载策略,让bundle体积大幅减小,加载速度翻倍

一句话总结:React.lazy 动态 import 组件实现代码分割,Suspense 包裹懒加载组件提供 loading fallback——两者配合让首屏只加载必要代码,按需加载路由级和组件级模块,bundle 体积和加载速度双赢!

一、Suspense 和 lazy 是什么?

问题背景:默认情况下 React 会把整个应用打包成一个 bundle,首屏加载所有代码,页面打开慢。React.lazy + Suspense 让你按需加载组件,首屏只加载必要代码。

特性直接 importReact.lazy
加载时机首次打包即加载渲染时才动态 import
bundle 体积全部在一个文件自动代码分割
需要 Suspense不需要必须(提供 loading)
SSR 支持完整需要额外处理
适用场景首屏组件路由/弹窗/重图表
// ❌ 直接 import:所有代码打进一个 bundle
import Dashboard from './pages/Dashboard'
import Settings from './pages/Settings'
import Reports from './pages/Reports'
// 三个页面全在首屏 bundle 里,打开慢

二、5 大高频场景 & 代码示例

场景 1:路由级懒加载 → 最常见

// ✅ 正确:路由级 lazy + Suspense
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Layout from './Layout'

const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
const Profile = lazy(() => import('./pages/Profile'))

function App() {
  return (
    <BrowserRouter>
      <Layout>
        <Suspense fallback={<PageLoading />}>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/dashboard" element={<Dashboard />} />
            <Route path="/settings" element={<Settings />} />
            <Route path="/profile" element={<Profile />} />
          </Routes>
        </Suspense>
      </Layout>
    </BrowserRouter>
  )
}

function PageLoading() {
  return <div className="loading">页面加载中...</div>
}
// ❌ 错误:Suspense 包了整个 Layout,布局也跟着闪
function App() {
  return (
    <Suspense fallback={<div>加载中</div>}>
      <BrowserRouter>
        <Layout>   {/* Layout 也会被 fallback 覆盖 */}
          <Routes>...</Routes>
        </Layout>
      </BrowserRouter>
    </Suspense>
  )
}

场景 2:弹窗/抽屉懒加载 → 用了才加载

// ✅ 正确:弹窗组件 lazy,点击时才加载代码
import { lazy, Suspense, useState } from 'react'

const HeavyModal = lazy(() => import('./components/HeavyModal'))

function App() {
  const [modalOpen, setModalOpen] = useState(false)

  return (
    <div>
      <button onClick={() => setModalOpen(true)}>打开弹窗</button>
      {modalOpen && (
        <Suspense fallback={<div>弹窗加载中...</div>}>
          <HeavyModal onClose={() => setModalOpen(false)} />
        </Suspense>
      )}
    </div>
  )
}
// ❌ 错误:首屏就 import 重的弹窗组件
import HeavyModal from './components/HeavyModal'  // 首屏就加载

场景 3:错误处理 → 配合 ErrorBoundary

// ✅ 正确:lazy 组件加载失败时用 ErrorBoundary 兜底
import { lazy, Suspense, Component } from 'react'

const RemoteConfig = lazy(() => import('./components/RemoteConfig'))

class ErrorBoundary extends Component {
  state = { hasError: false }

  static getDerivedStateFromError() {
    return { hasError: true }
  }

  componentDidCatch(error) {
    console.error('组件加载失败:', error)
  }

  render() {
    if (this.state.hasError) {
      return <div>组件加载失败,请刷新重试</div>
    }
    return this.props.children
  }
}

function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>加载中...</div>}>
        <RemoteConfig />
      </Suspense>
    </ErrorBoundary>
  )
}
// ❌ 错误:没有 ErrorBoundary,网络断了组件加载失败直接白屏
<Suspense fallback={<div>加载中...</div>}>
  <RemoteConfig />  {/* 加载失败 → 白屏,没有兜底 */}
</Suspense>

场景 4:预加载策略 → 鼠标 hover 时加载

// ✅ 正确:hover 时预加载,点击时已就绪
import { lazy, Suspense, useState } from 'react'

const ChartModal = lazy(() => import('./components/ChartModal'))

function Dashboard() {
  const [showChart, setShowChart] = useState(false)

  // hover 时触发预加载
  const handleMouseEnter = () => {
    import('./components/ChartModal')
  }

  return (
    <div>
      <button
        onClick={() => setShowChart(true)}
        onMouseEnter={handleMouseEnter}  // 预加载
      >
        查看图表
      </button>

      {showChart && (
        <Suspense fallback={<div>图表加载中...</div>}>
          <ChartModal onClose={() => setShowChart(false)} />
        </Suspense>
      )}
    </div>
  )
}

圦景 5:命名导出懒加载 → 包装函数处理

// ✅ 正确:组件是命名导出时,需要手动包装
import { lazy, Suspense } from 'react'

// ❌ 错误:命名导出不能直接用
// const Icon = lazy(() => import('./Icon'))  // 默认导出才有效

// ✅ 正确:手动从模块中取命名导出
const Icon = lazy(() =>
  import('./Icon').then(module => ({ default: module.Icon }))
)

const Header = lazy(() =>
  import('./Header').then(module => ({ default: module.Header }))
)

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Header />
      <Icon />
    </Suspense>
  )
}
// ❌ 错误:命名导出直接传给 lazy
const Icon = lazy(() => import('./Icon'))
// lazy 期望返回 { default: Component }
// 如果 Icon.tsx 里是 export const Icon = ...
// 则返回 { Icon: Component },没有 default → 报错

三、万能兜底:懒加载决策模板

场景是否懒加载原因
首屏组件(Layout/Header)❌ 直接 import首屏必加载,lazy 反而多一次请求
路由页面✅ lazy用户可能不访问所有页面
弹窗/抽屉✅ lazy用到才加载,省 bundle
图表/编辑器✅ lazy体积大,首屏不需要
小组件(Button/Icon)❌ 直接 import体积小,lazy 的 overhead 不值
第三方大库✅ lazy几百 KB 的库按需加载

万能模板:

import { lazy, Suspense, Component } from 'react'

// 1. ErrorBoundary 兜底
class LazyError extends Component {
  state = { hasError: false }
  static getDerivedStateFromError() { return { hasError: true } }
  render() {
    return this.state.hasError
      ? <div>加载失败,<button onClick={() => location.reload()}>重试</button></div>
      : this.props.children
  }
}

// 2. lazy 组件
const HeavyPage = lazy(() => import('./pages/HeavyPage'))

// 3. Suspense + ErrorBoundary 双保险
function App() {
  return (
    <LazyError>
      <Suspense fallback={<Skeleton />}>
        <HeavyPage />
      </Suspense>
    </LazyError>
  )
}

四、预防 checklist(不再踩坑)

五、一句话总结

lazy 动态 import 做代码分割,Suspense 提供加载兜底,ErrorBoundary 提供错误兜底——三件套配合让大应用首屏只加载必要代码,按需加载不怕慢。

到此这篇关于React中Suspense和lazy实现懒加载的几种场景的文章就介绍到这了,更多相关React中Suspense和lazy懒加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文