React query 服务器端渲染
date
Nov 14, 2025
slug
react-query-suspense
status
Published
tags
React Query
summary
使用 Suspense - 让 React 帮你管理加载状态 🎭
type
Post
哈喽各位!今天咱们聊个让代码更优雅的东西 —— Suspense。
先说说老问题:等待的艺术 ⏳
任何时候你处理服务器状态,都会有个尴尬期:你想要数据 和 你实际拿到数据 之间有个时间差。
更惨的是,数据可能永远不来——就像你在楼下等外卖,但骑手可能根本没接单 🛵
之前我们怎么处理的?检查 Query 的
status 是否为 pending,或者用 useQuery 给的 isLoading:这样做没毛病,但有个问题:加载逻辑和组件耦合了。
打个比方:你家每个房间都装了单独的温控器,想调整温度得挨个房间去按。能用,但要是有个中央空调统一控制,不是更爽?❄️
在基于组件的架构中,我们通常更希望有个更高层次、更统一的加载处理器,能管理应用中任何地方的加载状态。
这就是 Suspense 出场的时候了!
Suspense 是什么?一个统一的"加载管家" 🎩
如果你不熟悉,简单说:
Suspense 对于加载状态,就像 Error Boundaries 对于错误一样。
它们的工作方式也很像——你在想要设置加载边界的地方,把 children 包裹起来:
React 会显示你的
fallback(加载提示),直到 children 所需的所有代码和数据都准备好。🤓 小贴士
可能有人会想:"那我在
useEffect 或事件处理器里发异步请求,React 会显示 fallback 吗?"useSuspenseQuery:把异步生命周期交给 React 🚀
要配合 Suspense 使用 React Query,你需要用
useSuspenseQuery 钩子。它是怎么工作的?
使用
useSuspenseQuery,就像是把异步生命周期管理外包给了 React 本身:- React 看到
queryFn返回的 Promise
- 在 Promise 解决之前,显示 Suspense 的
fallback
- 如果 Promise 被拒绝,把错误转发到最近的 ErrorBoundary
看个基本例子:
一开始好处可能不明显,但想象一下:随着应用增长,有个统一的加载状态处理方式能大大简化事情。
更妙的是...
你可能没注意到,在
Repo 组件内部,我们不再需要检查 isLoading 或 status 了!使用
useSuspenseQuery,你可以假设 data 在组件渲染时一定可用,并且它与 pending 和 error 处理解耦了。Suspense 边界的灵活性:想粗想细都行 🎨
类似于 ErrorBoundaries,你可以在组件树中放任意数量的 Suspense 边界,粒度随你定:
场景 1:统一加载
多个查询放在一个边界里,React 会"收集"所有 Promise,直到全部解决才显示内容:
就像在餐厅点了两道菜,服务员等两道菜都好了才一起上 🍽️
场景 2:逐个显示
每个查询放单独的边界,数据准备好就立刻显示:
这次是哪道菜先好就先上哪道 🍜
这给了我们很好的方式来实现任何需要的用户体验——单独展示还是一起展示,你说了算!
⚠️ 不要追逐瀑布!
有个重要的点要注意:Suspense 依赖于组件组合。
如果你在同一个组件里多次调用
useSuspenseQuery,它们不会并行运行:就像你去银行办两件事,但只有一个窗口,得排两次队 🏦
解决方案
每个组件只用一个 Query,或者用
useSuspenseQueries 并行触发多个:useSuspenseQuery vs useQuery:有啥不同? 🆚
API 很相似,但有几个重要区别:
1. 不支持 enabled 选项
想想也合理:如果能通过
enabled 禁用查询,那就破坏了 useSuspenseQuery 的保证——调用时一定有 data。等等,那依赖查询怎么办?🤔
之前我们用
enabled 处理依赖查询:用 Suspense 的话,根本不用担心这个问题!
因为在同一组件中调用时,查询会串行执行,就像这样:
2. 不支持 placeholderData
这也说得通:
placeholderData 的目的是避免在获取数据时显示加载指示器。但用 Suspense,本身就是要显示 fallback 直到数据准备好,所以不需要
placeholderData。但我想显示旧数据怎么办?🤷♂️
细心的同学可能会问:在新数据加载时显示旧数据的场景怎么办?
之前我们用
placeholderData,还记得分页那个例子吗:useSuspenseQuery 不支持 placeholderData,那怎么办?答案:用 React 自己的能力! 🎉
使用 useTransition
React 有个内置概念叫 Transition(过渡),允许你在过渡期间继续显示旧数据,而不是卸载并显示 suspense fallback。
首先,把整个 App 包在边界里:
然后在 Hook 里把
useQuery 换成 useSuspenseQuery,去掉 placeholderData:魔法来了:useTransition
useTransition 返回两个东西:isPreviousData:布尔值,告诉你是否在过渡中
startTransition:启动过渡的函数
完整例子:
注意到通过把状态更新包在
startTransition 里,我们用 isPreviousData 替换了 isPlaceholderData。之前相同的功能和用户体验,但现在通过 React 内置的过渡系统实现! ✨
🍞 值得了解:自动的 staleTime
虽然我们没设
staleTime,但用 Suspense 时会自动有个短的 staleTime。为什么?因为 React 显示 suspense fallback 时会卸载组件树。如果没有短的 staleTime,组件准备好渲染时会立刻触发背景重新获取。
🪤 避坑:渲染时获取不是最佳实践
从组件中直接触发获取的概念叫 "渲染时获取"(Render-as-you-fetch),这不理想。
就像你走进厨房才想起来要点外卖,而不是提前下单 🍕
相反,你应该尽早获取,通常意味着在组件树的尽可能高处。
更好的做法:边获取边渲染
在 React 有机会渲染组件之前就预取:
合适的预取时机包括:
- 事件处理器(页面切换时)
- 路由加载器(集成路由器时)
- 服务器组件
总结一下 📝
Suspense 的威力在于:
✅ 统一的加载状态管理
✅ 组件无需处理自身的加载和错误状态
✅ 灵活的边界粒度
✅ 配合
useTransition 实现流畅的过渡但要注意:
⚠️ 避免在同一组件多次调用造成瀑布
⚠️ 尽早预取,不要等到渲染时才获取
React Suspense 让我们能写出更简洁的组件,但它的真正威力将在与服务器端渲染结合时释放——这个我们下节课见!
好了,今天的分享就到这里!Suspense 用好了,代码会优雅很多。有问题欢迎留言~ 🚀