它解决什么

经典写法:

const { data, loading } = useFetch(url);
if (loading) return <Spinner />;
return <List data={data} />;

每个组件自己管 loading,代码到处是 if (loading) ...

Suspense 把 loading 集中到一层

<Suspense fallback={<Spinner />}>
  <List />     {/* List 内部"挂起"时,Suspense 显示 fallback */}
</Suspense>

基本概念

某个组件渲染时挂起(suspend)——意思是"我数据没好"——React 沿组件树向上找最近的 <Suspense>,渲染它的 fallback。等组件 ready 再切回真实内容。

在 Next.js / RSC 中的用法

// app/page.tsx (Server Component)
import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>加载销售数据…</p>}>
        <Sales />
      </Suspense>
      <Suspense fallback={<p>加载用户数据…</p>}>
        <Users />
      </Suspense>
    </>
  );
}

async function Sales() {
  const data = await fetchSales();   // 慢接口
  return <SalesChart data={data} />;
}

服务器把 HTML 流式发给浏览器:先发 <h1> + 两个 fallback;接口好一个就替换一个,无需等全部好。

Client Components 的 Suspense

use() Hook(React 19)+ Suspense:

'use client';
import { use } from 'react';

function User({ promise }: { promise: Promise<UserData> }) {
  const user = use(promise);   // 挂起直到 promise 解决
  return <div>{user.name}</div>;
}

// 父组件
<Suspense fallback={<p>加载中</p>}>
  <User promise={fetchUser(id)} />
</Suspense>

注意promise 必须在父组件以稳定引用传入,否则会无限挂起。一般用 React Server Component 拿到 promise 再传给 Client Component。

TanStack Query 配 Suspense

const { data } = useSuspenseQuery({ queryKey: ['user', id], queryFn: ... });
// 数据没好时整个组件挂起 → 上层 Suspense fallback 显示

useSuspenseQuery 不返回 loading / error——loading 走 Suspense,error 走 Error Boundary(下一篇)。

多个 Suspense 的瀑布

<Suspense fallback={<A />}>
  <Sales />
  <Suspense fallback={<B />}>
    <Users />
  </Suspense>
</Suspense>

Sales 没好就显示 A;Sales 好了再开始渲染 Users,Users 没好显示 B。

避免瀑布:在父组件同时发起多个请求,而不是嵌套等待。

SuspenseList(实验中)

React 18 的实验 API,让多个 Suspense 按顺序显示(避免内容跳动)。状态:长期实验,生产别依赖

  • 不是所有数据获取都自动支持 Suspense——必须用支持 Suspense 的库(TanStack Query 的 useSuspenseQuery、Next.js 的 RSC、use() Hook)
  • 客户端组件里 fetch() 不会自动挂起——它返回 Promise 你得自己 await,自己 setState
  • Suspense 不捕获错误——错误要 Error Boundary(下一篇

心智模型

  • Suspense = 显示"加载中"的统一入口
  • Error Boundary = 显示"出错了"的统一入口
  • 两个一起包:
<ErrorBoundary fallback={<Err />}>
  <Suspense fallback={<Loading />}>
    <RealUI />
  </Suspense>
</ErrorBoundary>

→ 下一篇 Error Boundary