React中Suspense和lazy怎么用实现懒加载

📅 2026/8/7 17:23:29
React中Suspense和lazy怎么用实现懒加载
React 中 Suspense 和 lazy 怎么用实现懒加载一句话总结React.lazy 动态 import 组件实现代码分割Suspense 包裹懒加载组件提供 loading fallback——两者配合让首屏只加载必要代码按需加载路由级和组件级模块bundle 体积和加载速度双赢正文目录Suspense 和 lazy 是什么5 大高频场景 代码示例万能兜底懒加载决策模板预防 checklist不再踩坑一句话总结一、Suspense 和 lazy 是什么问题背景默认情况下 React 会把整个应用打包成一个 bundle首屏加载所有代码页面打开慢。React.lazySuspense让你按需加载组件首屏只加载必要代码。特性直接 importReact.lazy加载时机首次打包即加载渲染时才动态 importbundle 体积全部在一个文件自动代码分割需要 Suspense不需要必须提供 loadingSSR 支持完整需要额外处理适用场景首屏组件路由/弹窗/重图表// ❌ 直接 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 classNameloading页面加载中.../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直接 importlazy 组件必须被 Suspense 包裹否则报错网络可能失败的场景加 ErrorBoundary命名导出的组件用.then(m ({ default: m.X }))包装Suspense 的 fallback 不要太简陋用 Skeleton 提升体验大型应用配合 webpack magic comment 预加载/* webpackPrefetch: true */路由级 Suspense 放在Routes外层不要包 Layout不要在循环或条件中调用lazy()放在模块顶层五、一句话总结lazy 动态 import 做代码分割Suspense 提供加载兜底ErrorBoundary 提供错误兜底——三件套配合让大应用首屏只加载必要代码按需加载不怕慢。最后问候亲爱的朋友们并邀请你们阅读我的全新著作 《React进阶之路》