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 吗?"
不会的! Suspense 不是这么工作的,但这超出了本课范围。如果你想深入了解 Suspense 的工作原理,建议阅读 官方文档 或访问 react.gg

useSuspenseQuery:把异步生命周期交给 React 🚀

要配合 Suspense 使用 React Query,你需要用 useSuspenseQuery 钩子。

它是怎么工作的?

使用 useSuspenseQuery,就像是把异步生命周期管理外包给了 React 本身:
  1. React 看到 queryFn 返回的 Promise
  1. 在 Promise 解决之前,显示 Suspense 的 fallback
  1. 如果 Promise 被拒绝,把错误转发到最近的 ErrorBoundary
看个基本例子:
一开始好处可能不明显,但想象一下:随着应用增长,有个统一的加载状态处理方式能大大简化事情

更妙的是...

你可能没注意到,在 Repo 组件内部,我们不再需要检查 isLoadingstatus
使用 useSuspenseQuery,你可以假设 data 在组件渲染时一定可用,并且它与 pendingerror 处理解耦了。

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 用好了,代码会优雅很多。有问题欢迎留言~ 🚀

© 拾光 2025 - 2026

粤ICP备2025472574号