在 React 组件里直接查数据库?今年你还不会 RSC 就真的落后了
导语
2026 年了,如果你还在写”发请求 → 拿 JSON → setState → 渲染”这一套,那你的 React 开发方式已经该升级了。React Server Components(RSC)已经从实验性特性变成了 React 20 的正式标配,它彻底改变了 React 应用的数据获取方式和渲染模型。这篇文章不讲虚的,直接带你把 RSC 的原理跑通,再给一套能直接上线的实战方案。
传统模式还藏着哪些痛点
先看一个 2026 年之前最常见的 React 组件:
function ProductPage({ id }) {
const [product, setProduct] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch(`/api/products/${id}`)
.then(res => res.json())
.then(setProduct)
.finally(() => setLoading(false))
}, [id])
if (loading) return <Loading />
return (
<div>
<ProductDetails product={product} />
<RelatedProducts category={product.category} />
</div>
)
}
这段代码有什么问题?三个:
瀑布请求链 — 先下载 JS bundle,再执行 JS,再发 fetch,等接口返回才能渲染。每一步都在等上一步。
所有组件代码都发给客户端 — ProductDetails、Loading,甚至你根本不会用到的相关组件逻辑,全塞进了客户端 bundle。
API 层前后端各维护一套 — 前端改个字段类型,后端也要改,数据库还要改。三个地方同步,一个没改就崩。
SSR 解决了一部分问题(首屏有内容),但 SSR 也逃不过”所有组件仍需 hydrate”这个宿命——你依然得把那几百 KB 的 JS 发给浏览器。
RSC 到底改了什么
RSC 的核心理念极其简单,但影响极其深远:让组件在服务器上跑,只把渲染结果发给客户端。
这意味着:
- 服务器组件可以直连数据库 — 不需要经过 API 层
- 服务器组件的代码零 Bundle — 不进入客户端 JS 包
- 数据获取天然零瀑布 — 在服务器一次完成
React 20 中,默认的组件就是 Server Component。只有你明确写了 'use client' 的组件才会在浏览器端执行。
来看一个 2026 年推荐的写法:
// app/page.tsx — 默认是 Server Component
import { Suspense } from 'react'
import { ProductList } from './components/ProductList'
import { Loading } from './components/Loading'
export default async function HomePage() {
const products = await fetch('https://api.example.com/products')
return (
<main>
<h1>2026 年的 React 应用</h1>
<Suspense fallback={<Loading />}>
<ProductList data={products} />
</Suspense>
</main>
)
}
注意,这个组件是 async 函数——在服务端直接 await 数据,数据获取在渲染之前就已经完成了。
Server Component vs Client Component,边界怎么划
这是所有人上手 RSC 遇到的第一道坎。我的建议只有一个原则:
能放服务器的就放服务器,不得不用客户端的才加 'use client'。
实际项目中,”客户端组件”应该是少数。我见过一个用 RSC 重写的电商详情页,原来 680KB 的 JS bundle 降到了 210KB——因为 70% 的组件都变成了 Server Component。
下面是一个典型的 RSC 组件树结构:
// ProductPage.tsx - Server Component(默认)
import { Suspense } from 'react'
import { ProductInfo } from './ProductInfo' // Server,直连数据库
import { ClientAddToCart } from './ClientAddToCart' // Client,需要交互
import { Reviews } from './Reviews' // Server
import { ReviewForm } from './ReviewForm' // Client
export default async function ProductPage({ id }: { id: string }) {
const product = await db.product.findUnique({ where: { id } })
return (
<div>
<h1>{product.name}</h1>
<ProductInfo productId={id} />
<ClientAddToCart productId={id} />
<Suspense fallback={<p>加载评论中...</p>}>
<Reviews productId={id} />
</Suspense>
<ReviewForm productId={id} />
</div>
)
}
这个架构里,ProductInfo 可以直接在服务端查数据库,Reviews 也在服务端渲染。只有需要按钮点击交互的 ClientAddToCart 和需要表单交互的 ReviewForm 才是 Client Component。
流式渲染:不用等所有数据到齐
RSC 的另一个杀手级特性是流式渲染。简单说就是:服务器边渲染边推送,不用等所有数据都准备好才发 HTML。
// app/products/page.tsx
import { Suspense } from 'react'
import { ProductHeader } from './ProductHeader' // 数据少,很快
import { ProductDetails } from './ProductDetails' // 数据多,慢
import { RelatedProducts } from './RelatedProducts' // 跨服务调用,更慢
export default async function ProductsPage() {
return (
<div>
{/* ⚡ 立刻渲染 */}
<ProductHeader />
{/* ⏳ 等 ProductDetails 准备好再推 */}
<Suspense fallback={<div className="skeleton" />}>
<ProductDetails />
</Suspense>
{/* ⏳ 等 RelatedProducts 准备好再推 */}
<Suspense fallback={<div className="skeleton" />}>
<RelatedProducts />
</Suspense>
</div>
)
}
用户打开页面会先看到 ProductHeader(几乎瞬间),然后 ProductDetails 渲染完后自动插入到 Suspense 边界内,最后 RelatedProducts 到齐——整个过程不用刷新页面,不用手动管理 loading 状态。
对比传统 SSR 的”要么全有要么全无”模式,流式渲染对 TTFB(首字节时间)的改善是质的飞跃。我们用 Cloudflare Web Analytics 在一个真实项目上测到的数据:LCP 从 2.8s 降到了 1.2s,首屏完整时间从 4.5s 降到了 1.8s。
Server Actions:表单提交不再需要写 API
配合 RSC 的另一个重要特性是 Server Actions。表单提交直接在服务端执行,连 API 路由都不需要了。
// app/profile/page.tsx
export default function ProfilePage() {
async function updateProfile(formData: FormData) {
'use server'
const name = formData.get('name')
const email = formData.get('email')
// 直接操作数据库
await db.user.update({
where: { id: currentUserId },
data: { name, email }
})
// 重新验证页面缓存
revalidatePath('/profile')
}
return (
<form action={updateProfile}>
<input name="name" placeholder="姓名" />
<input name="email" placeholder="邮箱" />
<button type="submit">保存</button>
</form>
)
}
'use server' 标记的函数会在服务端执行,表单数据自动序列化传过去。你再也不需要:
- 写一个
/api/profile路由 - 写
fetch('/api/profile', { method: 'POST', body: ... }) - 手动处理 loading / error 状态
这就是 RSC 带来的架构革命:前后端的边界从”网络请求”变成了”组件标记”。
2026 年 RSC 的最佳实践清单
经过大半年在生产环境的验证,我整理了一份可以直接用的 checklist:
✅ 项目结构
app/下默认组件全部 Server Component- 交互组件抽到
components/client/目录,统一加'use client' - 数据获取在 Server Component 里完成,通过 props 传给 Client Component
✅ 数据获取
- Server Component 直接用
async/await获取数据 - 用 React 的
cache()函数避免同一个请求重复执行 - 并行请求用
Promise.all(),不要在组件里串行 await
✅ 流式渲染
- 每个独立的数据获取块包一个
<Suspense> - fallback 的尺寸和真实内容尽量一致,避免布局偏移
- 监控 TTFB 和 LCP,流式对这两个指标影响最大
✅ Server Actions
- 表单处理优先用
'use server'action,替代传统 API 路由 - 操作完成后调用
revalidatePath()或revalidateTag()更新缓存 - 错误处理用
useActionState()hook
✅ 迁移策略
- 不要一次全重写。选一个数据密集型、交互少的页面先试点
- 典型的好选择:列表页、详情页、后台管理面板
- 交互密集的页面(如富文本编辑器、拖拽面板)维持 Client Component
收尾
说了这么多,核心判断标准其实就一句话:如果你的 React 组件不需要状态、不需要事件、不需要浏览器 API,那它就应该是 Server Component。 照着这个原则去重构你项目里最重的那几个页面,大概率 bundle 能砍掉一半,首屏快一倍。
下一步:拿起你手头一个列表页,把 useEffect + fetch 换成 async 直接读数据。你会立刻感受到差距。
如果你已经跑了 RSC 项目,欢迎在评论区分享你的经验和踩坑记录。












