React Suspense 数据请求:骨架屏、失败重试一次学会
·
当你打开一个页面,看到骨架屏平滑过渡到真实内容,那背后很可能是
React.Suspense和数据请求的合作。
🎯 Suspense 做什么?
- 等待异步数据(组件、请求)准备好。
- 数据还没回来时显示
fallback(骨架屏/加载动画)。 - 数据准备好后自动替换成真实内容。
🧱 基础用法
<Suspense fallback={<p>加载中...</p>}>
<UserProfile id={123} />
</Suspense>
UserProfile内部如果“暂停”了(抛出一个 Promise),Suspense 会捕获并显示 fallback。
⚙️ 数据请求工具:自定义资源封装
function createResource(promise) {
let status = 'pending';
let result;
const suspender = promise.then(
(res) => {
status = 'success';
result = res;
},
(err) => {
status = 'error';
result = err;
}
);
return {
read() {
if (status === 'pending') throw suspender;
if (status === 'error') throw result;
return result;
},
};
}
read()会在数据没准备好时抛出 Promise,让 Suspense 接管。- 数据错误时抛出 Error,交给错误边界处理。
📦 创建用户数据资源
const userResource = createResource(
fetch('/api/user/123').then((res) => res.json())
);
function UserProfile() {
const user = userResource.read();
return (
<div>
<h2>{user.name}</h2>
<p>邮箱:{user.email}</p>
</div>
);
}
🧯 错误边界搭配
Suspense 只处理“等待”,错误要靠 Error Boundary。
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} onRetry={this.props.onRetry} />;
}
return this.props.children;
}
}
🧩 完整示例:带重试按钮
function App() {
const [resource, setResource] = useState(() => fetchUser());
const retry = () => {
setResource(fetchUser());
};
return (
<ErrorBoundary onRetry={retry}>
<Suspense fallback={<Skeleton />}>
<UserProfile resource={resource} />
</Suspense>
</ErrorBoundary>
);
}
function UserProfile({ resource }) {
const user = resource.read();
return (
<div>
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
🎨 骨架屏写法
function Skeleton() {
return (
<div className="skeleton">
<div className="avatar" />
<div className="line" />
<div className="line short" />
</div>
);
}
- 通过 CSS 动画模拟 “骨架闪烁” 效果。
- fallback 可以是骨架屏、loading、占位图,灵活选择。
⚙️ 和 React Query/Relay 等库搭配
- React Query:
<Suspense>处理 loading,useQuery里开启suspense: true。 - Relay:天然支持 Suspense,只需用
<Suspense>包裹数据组件。 - SWR:
suspense: true+<Suspense>即可。
function UserProfile({ id }) {
const { data } = useQuery(['user', id], fetchUser, { suspense: true });
return <div>{data.name}</div>;
}
🧯 常见坑
| 坑 | 现象 | 解决 |
|---|---|---|
| Suspense 没生效 | 组件没抛 Promise | 确保数据加载时 throw Promise(库需开启 suspense) |
| 错误没显示 | 没有 ErrorBoundary | Suspense 只处理 pending 状态 |
| fallback 闪烁 | 请求太快 | 加延迟:setTimeout 或 startTransition |
| 多层 Suspense 嵌套混乱 | fallback 嵌套难维护 | 使用多个 Suspense 包裹不同区域 |
🏁 小练习
- 用
Suspense包裹多个组件:用户信息 + 动态列表,分别加载。 - 写一个
useResourceHook 封装createResource,复用在项目中。 - 研究 Next.js 13 的
app目录,看看它如何内置 Suspense 数据加载。
更多推荐


所有评论(0)