深色模式
React 20 的 Suspense 经历了自 18 以来最大的一次迭代。新增的 SuspenseList 组件、流式 SSR 改进、以及与 Actions 的深度集成,让 Suspense 从「加载态占位」进化为「数据获取编排」。
SuspenseList:控制加载顺序
SuspenseList 解决了多个异步组件同时加载时的视觉混乱问题——你可以控制它们的出现顺序。
javascript
import { Suspense, SuspenseList } from 'react';
function Dashboard() {
return (
<SuspenseList revealOrder="forwards" tail="collapsed">
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<UserGrowthChart />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<ConversionChart />
</Suspense>
</SuspenseList>
);
}revealOrder 的取值:
"forwards":按顺序展示,前面的没加载完,后面的不显示"backwards":反向顺序"together":全部加载完后一起展示undefined:谁先好谁先出(默认行为)
tail="collapsed" 表示还没轮到的组件不显示 fallback,避免了一排 skeleton 的视觉噪音。
javascript
// 实际场景:聊天消息列表
function ChatMessages({ messages }) {
return (
<SuspenseList revealOrder="forwards" tail={3}>
{messages.map(msg => (
<Suspense key={msg.id} fallback={<MessageSkeleton />}>
<MessageRenderer id={msg.id} />
</Suspense>
))}
</SuspenseList>
);
}
// tail={3} 表示最多显示 3 个 fallback skeleton
// 超出的部分完全隐藏,避免页面闪烁useSuspenseQuery:数据获取新范式
React 20 将 use hook 和 Suspense 结合,推出了 useSuspenseQuery,替代了传统的 useEffect + fetch 模式。
javascript
import { useSuspenseQuery } from '@tanstack/react-query';
// 也可以直接用 React 内置的
import { use, Suspense } from 'react';
// 方案一:React Query 集成
function UserProfile({ userId }) {
const { data: user } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => api.getUser(userId),
staleTime: 5 * 60 * 1000,
});
return (
<div>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
// 方案二:React 原生 use hook
function UserProfileNative({ userPromise }) {
const user = use(userPromise); // 自动触发 Suspense
return (
<div>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
</div>
);
}
// 使用方式
function App({ params }) {
const userPromise = api.getUser(params.id); // 在 Server Component 中发起
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfileNative userPromise={userPromise} />
</Suspense>
);
}关键区别:use hook 可以接受 Promise 作为参数,它会在组件渲染时挂起(suspend),而不是在 effect 中异步设置状态。这让数据获取和渲染合为一体。
流式 SSR 的 Selective Hydration 改进
React 20 的流式 SSR 现在支持选择性注水——用户交互的组件优先注水,其他的延迟处理。
javascript
// app/layout.tsx (Next.js 16)
import { Suspense } from 'react';
export default function RootLayout({ children }) {
return (
<html>
<body>
<header>
<Navigation /> {/* 交互组件,优先注水 */}
</header>
<main>
{children}
</main>
<footer>
<Suspense fallback={null}>
<AnalyticsWidget /> {/* 延迟注水,不影响首屏 */}
</Suspense>
</footer>
</body>
</html>
);
}用户点击导航时,React 会优先注水 <Navigation> 组件,即使页面其他部分还没完成注水。这显著改善了 TTI(Time to Interactive)。
嵌套 Suspense 的回退策略
React 20 允许 Suspense 边界指定回退显示的延迟时间,避免快速加载场景下的闪烁:
javascript
function App() {
return (
<Suspense
fallback={<PageSkeleton />}
// 延迟 200ms 显示 fallback,如果 200ms 内加载完就不显示
unstable_avoidFallback={true}
unstable_expectedLoadTime={200}
>
<Suspense
fallback={<SectionSkeleton />}
unstable_expectedLoadTime={50}
>
<SlowDataComponent />
</Suspense>
<FastComponent />
</Suspense>
);
}unstable_expectedLoadTime 给 React 一个提示,告诉它这个边界内的内容大概多久能加载好。React 会根据这个提示决定是否要显示 fallback——如果预计加载时间很短,就保持上一个状态不闪。
小结
- SuspenseList 控制多个异步组件的展示顺序,解决视觉混乱问题
usehook 让数据获取和渲染合一,彻底替代 useEffect + fetch- 流式 SSR 的 Selective Hydration 大幅改善 TTI,交互组件优先注水
- Suspense 边界支持延迟回退,避免快速加载场景下的 UI 闪烁
- Suspense 已从「加载占位」演变为完整的数据获取编排方案