2.4 KiB
2.4 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| 战略性的 Suspense 边界 | HIGH | 更快的首次绘制 | async, suspense, streaming, layout-shift |
战略性的 Suspense 边界
不要在异步组件中等待数据后再返回 JSX,而应使用 Suspense 边界在数据加载期间更快地展示外层 UI。
错误做法(外层 UI 被数据请求阻塞):
async function Page() {
const data = await fetchData() // 阻塞整个页面
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<div>
<DataDisplay data={data} />
</div>
<div>Footer</div>
</div>
)
}
整个布局都要等待数据,即使只有中间部分需要它。
正确做法(外层 UI 立即显示,数据流式传入):
function Page() {
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<div>
<Suspense fallback={<Skeleton />}>
<DataDisplay />
</Suspense>
</div>
<div>Footer</div>
</div>
)
}
async function DataDisplay() {
const data = await fetchData() // 仅阻塞该组件
return <div>{data.content}</div>
}
Sidebar、Header 和 Footer 立即渲染。只有 DataDisplay 等待数据。
备选方案(在组件间共享 Promise):
function Page() {
// 立即发起请求,但不等待
const dataPromise = fetchData()
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<Suspense fallback={<Skeleton />}>
<DataDisplay dataPromise={dataPromise} />
<DataSummary dataPromise={dataPromise} />
</Suspense>
<div>Footer</div>
</div>
)
}
function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise) // 解包 Promise
return <div>{data.content}</div>
}
function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise) // 复用同一个 Promise
return <div>{data.summary}</div>
}
两个组件共享同一个 Promise,因此只发起一次请求。布局立即渲染,同时两个组件一起等待。
何时不应使用此模式:
- 影响布局决策的关键数据(影响定位)
- 首屏之上对 SEO 至关重要的内容
- 小型、快速的查询,Suspense 开销不值得
- 当你想避免布局偏移时(加载 → 内容跳变)
权衡取舍: 更快的首次绘制 vs 潜在的布局偏移。根据你的用户体验优先级进行选择。