React Suspense 是 React 用于处理异步操作(如数据加载、代码分割、资源加载等)的核心特性,其核心作用是在等待异步操作完成时,显示预设的 "备用内容"(fallback) ,从而优化用户体验,避免页面空白或交互卡顿。
实际应用场景
1. 代码分割(Code Splitting):优化首屏加载
大型应用中,将所有代码打包到一个文件会导致初始加载缓慢。Suspense 配合 React.lazy
可以实现组件的动态导入,只在需要时加载对应代码,同时显示加载状态。
示例:路由级代码分割路由是最常见的代码分割场景,每个页面组件只在用户访问时加载:
jsx
javascript
import { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Loading from './Loading'; // 加载指示器组件
// 动态导入页面组件(只在访问时加载)
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const User = lazy(() => import('./pages/User'));
function App() {
return (
<Router>
{/* Suspense 包裹所有动态导入的组件,指定加载时显示的内容 */}
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user" element={<User />} />
</Routes>
</Suspense>
</Router>
);
}
- 效果:用户首次访问
/about
时,浏览器会异步加载About
组件的代码,加载期间显示Loading
组件(如骨架屏、 spinner)。 - 优势:减少首屏 JS 体积,提升初始加载速度。
2. 数据加载(Suspense for Data Fetching):统一管理加载状态
在数据驱动的应用中,组件往往需要等待接口返回数据后再渲染。Suspense 可以在数据加载期间自动显示 fallback ,替代传统的 "手动维护 loading 状态"(如 isLoading ? <Loading /> : <Content />
)。
注意 :React 本身不直接处理数据请求逻辑,需要结合支持 Suspense 的数据库(如 Relay、React Query 的 suspense: true
模式,或自定义实现)。
示例:配合 React Query 实现数据加载
jsx
javascript
import { Suspense } from 'react';
import { useQuery } from '@tanstack/react-query';
import Loading from './Loading';
// 数据请求函数(返回 Promise)
const fetchUser = (userId) =>
fetch(`/api/users/${userId}`).then(res => res.json());
// 数据组件(依赖异步数据)
function UserProfile({ userId }) {
// 启用 suspense 模式,数据加载时会暂停渲染并抛出 Promise
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
suspense: true, // 关键:让查询支持 Suspense
});
return <div>用户名:{data.name}</div>;
}
// 父组件:用 Suspense 包裹数据组件
function App() {
return (
<div>
<h1>用户信息</h1>
{/* 等待 UserProfile 加载数据时,显示 Loading */}
<Suspense fallback={<Loading />}>
<UserProfile userId="123" />
</Suspense>
</div>
);
}
-
优势:
- 消除手动管理
isLoading
状态的样板代码; - 支持 "并行加载"(多个数据请求同时触发,Suspense 等待所有请求完成后再渲染);
- 配合 React 并发特性(如
startTransition
),可实现 "非阻塞加载",不冻结 UI。
- 消除手动管理
3. 资源加载:图片、字体等媒体资源
对于大型图片、字体等资源,Suspense 可以统一处理加载状态,避免资源未加载完成时的布局偏移(CLS)。
示例:自定义图片组件配合 Suspense
jsx
ini
import { Suspense, useState, useEffect } from 'react';
import ImageSkeleton from './ImageSkeleton'; // 图片骨架屏
// 支持 Suspense 的图片组件
function SuspenseImage({ src, alt }) {
const [imageLoaded, setImageLoaded] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => setImageLoaded(true);
img.onerror = (e) => setError(e);
}, [src]);
// 图片未加载完成时,抛出 Promise 让 Suspense 捕获
if (!imageLoaded && !error) {
throw new Promise(resolve => {
const img = new Image();
img.src = src;
img.onload = resolve;
});
}
if (error) throw error;
return <img src={src} alt={alt} />;
}
// 使用:用 Suspense 包裹图片组件
function Gallery() {
return (
<div className="gallery">
<Suspense fallback={<ImageSkeleton />}>
<SuspenseImage
src="/large-image.jpg"
alt="风景照片"
/>
</Suspense>
</div>
);
}
- 效果:图片加载期间显示骨架屏,加载完成后平滑切换到图片。
4. 配合 Error Boundary 处理错误
异步操作(如代码加载失败、接口报错)可能抛出错误,Suspense 本身不处理错误,需配合 Error Boundary 捕获错误并显示友好提示。
示例:错误边界 + Suspense
jsx
javascript
// 错误边界组件
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>加载失败,请重试</div>;
}
return this.props.children;
}
}
// 使用:包裹 Suspense
function App() {
return (
<ErrorBoundary fallback={<div>用户信息加载失败</div>}>
<Suspense fallback={<Loading />}>
<UserProfile userId="123" />
</Suspense>
</ErrorBoundary>
);
}
- 效果:若
UserProfile
数据加载失败,Error Boundary 会捕获错误并显示 "用户信息加载失败"。
注意事项
- 使用时机 :Suspense 必须在组件渲染阶段 触发异步操作(如导入组件、执行数据查询),不能在事件处理函数(如
onClick
)中使用。 - 兼容性 :代码分割(配合
React.lazy
)是稳定特性;数据加载(Suspense for Data Fetching)在 React 18+ 中已支持,但需依赖数据库配合。 - 避免过度嵌套:多个异步操作可共享一个 Suspense,减少冗余的 fallback 显示。
总结
Suspense 的核心价值是将 "等待异步操作" 的逻辑从组件中抽离 ,让开发者更专注于业务逻辑,同时通过统一的 fallback 机制提升用户体验。实际项目中,最常用的场景是路由级代码分割 和数据加载状态管理,配合 Error Boundary 可构建更健壮的应用。