React.lazy 和 Suspense 如何使用?原理是什么?

基本用法

import { lazy, Suspense } from 'react'

// 懒加载组件
const Dashboard = lazy(() => import('./Dashboard'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Dashboard />
    </Suspense>
  )
}

路由级懒加载

import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

原理

  1. React.lazy 返回一个特殊组件,内部状态为 pending
  2. 触发 import() 动态加载模块(返回 Promise)
  3. React 渲染时发现组件处于 pending,抛出 Promise
  4. 最近的 Suspense 边界捕获 Promise,渲染 fallback
  5. Promise resolve 后(模块加载完成),React 重新渲染,显示真实组件
// React.lazy 简化实现
function lazy(loader) {
  let Component = null
  return function LazyComponent(props) {
    if (!Component) {
      // 抛出 Promise,Suspense 会捕获
      throw loader().then(mod => {
        Component = mod.default
      })
    }
    return <Component {...props} />
  }
}

多个懒加载组件

<Suspense fallback={<Loader />}>
  <Header />   {/* 先加载完 */}
  <Main />     {/* 后加载完 */}
  <Footer />   {/* 最后加载完 */}
</Suspense>
// 三个都加载完才隐藏 fallback

// 嵌套 Suspense:各部分独立加载
<Suspense fallback={<PageLoader />}>
  <Header />
  <Suspense fallback={<MainLoader />}>
    <Main />
  </Suspense>
  <Footer />
</Suspense>

React 18 服务端 Suspense

// 服务端也可以用 Suspense 流式渲染
// HTML 分块发送,先发送框架,后发送数据
<Suspense fallback={<Spinner />}>
  <Comments /> {/* 数据加载完成后流式注入 */}
</Suspense>

与 Error Boundary 配合

// 加载失败时的处理
<ErrorBoundary fallback={<Error />}>
  <Suspense fallback={<Loader />}>
    <LazyComponent />
  </Suspense>
</ErrorBoundary>

优势

同分类其他题目