Files
2026-07-13 21:36:17 +08:00

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 潜在的布局偏移。根据你的用户体验优先级进行选择。