它解决什么
经典写法:
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