文章目录
-
- [一、Suspense 是什么?](#一、Suspense 是什么?)
- 二、核心作用与价值
-
- [1. 代码分割(Code Splitting)](#1. 代码分割(Code Splitting))
- [2. 数据获取(Data Fetching)](#2. 数据获取(Data Fetching))
- [3. 用户体验优化](#3. 用户体验优化)
- 三、基础使用模式
-
- [1. 基本语法结构](#1. 基本语法结构)
- [2. 嵌套 Suspense](#2. 嵌套 Suspense)
- [3. 错误边界配合](#3. 错误边界配合)
- 四、高级应用场景
-
- [1. 过渡更新(startTransition)](#1. 过渡更新(startTransition))
- [2. 流式 SSR](#2. 流式 SSR)
- [3. 竞态处理](#3. 竞态处理)
- 五、实现原理剖析
-
- [1. 渲染生命周期](#1. 渲染生命周期)
- [2. 协调过程](#2. 协调过程)
- [3. 与 Concurrent 模式的协同](#3. 与 Concurrent 模式的协同)
- 六、最佳实践指南
-
- [1. 设计原则](#1. 设计原则)
- [2. 性能优化](#2. 性能优化)
- [3. 避免的陷阱](#3. 避免的陷阱)
一、Suspense 是什么?
Suspense 是 React 16.6 引入的一种机制,允许组件"等待"某些操作完成(如数据获取或代码加载)时,显示指定的加载状态。它代表了 React 异步渲染模式的重要进化。
也就是先展示一个占位符,从而实现更自然流畅的用户界面更新体验。
js
import { Suspense } from "react";
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<AsyncComponent />
</Suspense>
);
}
二、核心作用与价值
1. 代码分割(Code Splitting)
结合 React.lazy 实现按需加载组件
js
const Dashboard = React.lazy(() => import("./Dashboard"));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Dashboard />
</Suspense>
);
}
2. 数据获取(Data Fetching)
与支持 Suspense 的数据库(如 Relay、SWR)配合使用
js
function Profile() {
const data = fetchData(); // 支持Suspense的获取方式
return <h1>{data.name}</h1>;
}
function App() {
return (
<Suspense fallback={<ProfileSkeleton />}>
<Profile />
</Suspense>
);
}
3. 用户体验优化
- 避免组件加载时的布局跳动
- 实现更流畅的过渡效果
- 支持更复杂的加载状态管理
三、基础使用模式
1. 基本语法结构
js
<Suspense fallback={加载中UI}>
<异步组件或数据依赖组件 />
</Suspense>
2. 嵌套 Suspense
js
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<Content />
</Suspense>
<Footer />
</Suspense>
3. 错误边界配合
js
<ErrorBoundary>
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
四、高级应用场景
1. 过渡更新(startTransition)
js
function App() {
const [resource, setResource] = useState(initialResource);
const handleClick = () => {
startTransition(() => {
setResource(fetchNewData());
});
};
return (
<Suspense fallback={<Spinner />}>
<button onClick={handleClick}>加载新数据</button>
<Profile resource={resource} />
</Suspense>
);
}
2. 流式 SSR
js
// 服务端
const stream = renderToPipeableStream(<App />, {
onShellReady() {
stream.pipe(res);
}
});
// 客户端
hydrateRoot(document.getElementById("root"), <App />);
3. 竞态处理
js
function SearchResults({ query }) {
const data = fetchData(query); // Suspense集成
return (
<ul>
{data.results.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
五、实现原理剖析
1. 渲染生命周期
- 遇到 Suspense 边界
- 检查子组件是否"挂起"(suspended)
- 显示 fallback UI
- 异步操作完成后重新尝试渲染
2. 协调过程
- React 会记录挂起的组件
- 不立即提交挂起组件的 DOM 变更
- 等待所有依赖就绪后一次性更新
3. 与 Concurrent 模式的协同
js
// 启用并发模式
const root = createRoot(document.getElementById("root"));
root.render(
<React.unstable_ConcurrentMode>
<App />
</React.unstable_ConcurrentMode>
);
六、最佳实践指南
1. 设计原则
| 原则 | 说明 |
|---|---|
| 粒度控制 | 每个 Suspense 边界只包裹必要的组件 |
| 渐进加载 | 关键内容优先加载,次要内容延迟 |
| 错误处理 | 每个 Suspense 边界应有对应的错误边界 |
2. 性能优化
js
// 预加载示例
const OtherComponent = React.lazy(() => import("./OtherComponent"));
// 鼠标悬停时预加载
function LazyLoader() {
const [isHovered, setIsHovered] = useState(false);
useEffect(() => {
if (isHovered) {
OtherComponent.preload();
}
}, [isHovered]);
return (
<div onMouseEnter={() => setIsHovered(true)}>
<Suspense fallback={<Spinner />}>
<OtherComponent />
</Suspense>
</div>
);
}
3. 避免的陷阱
- 不要在
Suspense边界内使用useLayoutEffect - 避免在服务端渲染时过度依赖
Suspense - 不要忘记处理错误边界