FumadocsZDecode
React

React Suspense 组件

Suspense 是 React 提供的一个内置组件,用于声明式地处理异步操作的加载状态。它让组件能够"暂停"渲染,直到异步操作完成后再显示内容,同时展示一个 fallback 占位符。


一、核心概念

Suspense 通过捕获组件中的 Promise,实现优雅的异步加载处理,无需手动管理 loading 状态。

基本语法

<Suspense fallback={<Loading />}>
  <AsyncComponent />
</Suspense>
  • fallback:加载中显示的占位内容
  • 子组件:需要加载的异步组件

二、三个主要用途

1. 代码分割(Code Splitting)

配合 React.lazy() 实现组件按需加载:

import { Suspense, lazy } from 'react'

const HeavyComponent = lazy(() => import('./HeavyComponent'))
const AnotherComponent = lazy(() => import('./AnotherComponent'))

function App() {
  return (
    <div>
      <Suspense fallback={<p>加载中...</p>}>
        <HeavyComponent />
      </Suspense>

      <Suspense fallback={<p>另一个组件加载中...</p>}>
        <AnotherComponent />
      </Suspense>
    </div>
  )
}

2. 异步数据获取

使用支持 Suspense 的数据获取方案(如 Next.js、Relay):

function User({ userId }: { userId: number }) {
  const user = use(fetchUser(userId)) // 返回 Promise
  return <div>{user.name}</div>
}

function UserProfile({ userId }: { userId: number }) {
  return (
    <Suspense fallback={<p>加载用户信息...</p>}>
      <User userId={userId} />
    </Suspense>
  )
}

3. 嵌套 Suspense 细粒度控制

为不同的异步操作设置不同的 fallback,实现渐进式加载:

function ProfilePage() {
  return (
    <Suspense fallback={<PageLoading />}>
      <ProfileHeader />

      <Suspense fallback={<PostsLoading />}>
        <PostList />
      </Suspense>

      <Suspense fallback={<CommentsLoading />}>
        <CommentSection />
      </Suspense>
    </Suspense>
  )
}

三、避免不必要的重渲染

当状态变化导致新的异步操作时,已有内容不应该被 fallback 替换。使用 startTransition 标记状态更新为非紧急:

import { startTransition, useState, Suspense } from 'react'

function TabSwitcher() {
  const [tab, setTab] = useState('home')

  const handleTabChange = (newTab: string) => {
    // startTransition 让 React 在后台加载新内容
    // 不会立即显示 fallback,保留原有内容
    startTransition(() => {
      setTab(newTab)
    })
  }

  return (
    <div>
      <button onClick={() => handleTabChange('home')}>首页</button>
      <button onClick={() => handleTabChange('profile')}>个人资料</button>

      <Suspense fallback={<p>加载中...</p>}>
        {tab === 'home' && <HomePage />}
        {tab === 'profile' && <ProfilePage />}
      </Suspense>
    </div>
  )
}

四、与 ErrorBoundary 配合

Suspense 处理加载状态,ErrorBoundary 处理错误情况:

class ErrorBoundary extends React.Component<any, any> {
  state = { hasError: false }

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

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

function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<p>加载中...</p>}>
        <AsyncComponent />
      </Suspense>
    </ErrorBoundary>
  )
}

五、最佳实践

选择合适的 fallback

// ✓ 好:轻量级占位符
function Demo(params: type) {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Component />
    </Suspense>
  )
}

// ✓ 好:骨架屏提供更好的体验
function Demo(params: type) {
  return (
    <Suspense fallback={<SkeletonCard />}>
      <UserCard />
    </Suspense>
  )
}

// ✗ 避免:过于复杂的 fallback
function Demo(params: type) {
  return (
    <Suspense fallback={<ComplexLoadingAnimation />}>
      <Component />
    </Suspense>
  )
}

合理设置 Suspense 边界

// ✓ 好:为不同部分设置不同的加载状态
function Demo(params: type) {
  return (
    <Suspense fallback={<HeaderSkeleton />}>
      <Header />
    </Suspense>
  )
}

function Demo(params: type) {
  return (
    <Suspense fallback={<ContentSkeleton />}>
      <MainContent />
    </Suspense>
  )
}
// ✗ 避免:单个 Suspense 包裹整个页面
function Demo(params: type) {
  return (
    <Suspense fallback={<PageLoading />}>
      <Header />
      <MainContent />
      <Footer />
    </Suspense>
  )
}

避免过度嵌套

// ✗ 避免:过多的嵌套层级
function Demo(params: type) {
  return (
    <Suspense>
      <Suspense>
        <Suspense>
          <Component />
        </Suspense>
      </Suspense>
    </Suspense>
  )
}

// ✓ 正确:按功能区域分层
function Demo(params: type) {
  return (
    <Suspense fallback={<PageLoader />}>
      <Header />
      <Suspense fallback={<ContentLoader />}>
        <MainContent />
      </Suspense>
      <Footer />
    </Suspense>
  )
}

六、支持的场景

场景支持情况说明
React.lazy✅ 支持代码分割,按需加载组件
Next.js 数据获取✅ 支持服务端获取数据,客户端等待
流式 SSR✅ 支持逐步向浏览器发送 HTML
普通 fetch❌ 不支持需要使用支持 Suspense 的库
useEffect 中的异步❌ 不支持需要改为在组件外包装 Promise

核心要点:Suspense 通过声明式语法优雅地处理异步加载。结合 React.lazy 做代码分割,结合 startTransition 避免闪屏,结合 ErrorBoundary 处理错误。关键是合理设置 Suspense 边界,为不同区域指定不同的 fallback,而不是整页加载。

On this page