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,而不是整页加载。