React Server Components 和传统 SSR 有什么区别?
简化版
传统 SSR 是服务端把组件渲染成 HTML,再由客户端 hydrate 变得可交互;React Server Components 是让部分组件只在服务端运行,输出可被客户端合并的组件载荷,不把这些组件代码打进客户端包。RSC 主要解决数据获取靠近服务端、减少客户端 JS 和保护服务端能力的问题,不等同于 SSR。
详细版
传统 SSR 的核心产物是 HTML,客户端仍需要下载对应组件 JS 做 hydration。RSC 的核心产物是服务端组件树描述,Server Component 可以直接访问数据库、文件系统或服务端 API,但不能使用浏览器事件和客户端状态;需要交互时要切到 Client Component。
Server Component: 取数据、拼结构、减少客户端 JS
Client Component: useState、useEffect、onClick、浏览器 API
在 Next.js App Router 中,组件默认是 Server Component,写 'use client' 才进入客户端边界。面试要强调:RSC 可以和 SSR、SSG、Streaming 一起使用,它是组件执行位置的模型,不是单独的渲染模式。
完整版教学
一、先分清两个问题
SSR 回答的是“HTML 在哪里生成”,RSC 回答的是“组件代码在哪里执行”。这两个问题经常一起出现,但不是同一个维度。
| 问题 | SSR | RSC |
|---|---|---|
| 关注点 | 首屏 HTML 生成 | 组件执行环境 |
| 主要产物 | HTML | 服务端组件载荷 |
| 客户端 JS | 通常仍要 hydrate | Server Component 不进客户端包 |
| 数据获取 | 页面级或服务端阶段 | 组件级靠近数据源 |
记忆钩子:SSR 管“先吐 HTML”,RSC 管“哪些组件不用下发 JS”。
二、传统 SSR 的工作流
浏览器请求 /product/1
-> Node 服务端执行 React 渲染
-> 返回 HTML
-> 浏览器下载 JS
-> hydrate 绑定事件
SSR 的优势是首屏有 HTML,SEO 和首屏感知更好。代价是服务端每次请求要渲染,客户端仍要下载组件 JS 并 hydration。如果页面有 200KB 交互组件,SSR 只解决 HTML 首屏,不自动消除这 200KB JS。
三、RSC 的工作流
Server Component 执行在服务端
-> 读取数据
-> 生成组件树描述
-> Client Component 边界下载 JS 并交互
Server Component 可以写成异步组件,直接在服务端取数据:
async function ProductPage({ id }) {
const product = await db.product.findUnique({ where: { id } })
return <ProductView product={product} />
}
这段代码不会作为浏览器 JS 下发,因此数据库访问逻辑和依赖不会进入客户端包。真正需要点击、状态、浏览器 API 的部分再放到 Client Component。
四、Server 和 Client 边界
'use client'
export function AddToCartButton({ id }) {
const [loading, setLoading] = useState(false)
return <button onClick={() => setLoading(true)}>加入购物车</button>
}
'use client' 标记的是模块边界。被它标记的组件及其客户端依赖会进入客户端 bundle,可以使用 useState、useEffect、事件处理和浏览器 API。
Server Component 可以引用 Client Component,但 Client Component 不能直接导入 Server Component。否则客户端包会试图包含服务端能力,边界就乱了。
五、RSC 带来的收益
| 收益 | 原因 |
|---|---|
| 减少客户端 JS | 服务端组件代码不下发 |
| 数据获取靠近数据源 | 服务端组件可直接访问服务端资源 |
| 安全边界更清晰 | 密钥和数据库逻辑留在服务端 |
| 组合更细 | 组件级拆分服务端和客户端职责 |
假设一个 Markdown 渲染库 80KB,只用于把文章内容渲成静态结构。放在 Client Component 会增加客户端 JS;放在 Server Component 中,用户只拿到渲染结果,不需要下载这 80KB 渲染库。
六、RSC 的限制和心智成本
Server Component 不能使用浏览器 API,不能绑定事件,也不能使用客户端状态 Hook。它还要求数据必须可序列化地传给 Client Component,函数、数据库连接、类实例等不能随便跨边界传递。
Server -> Client props:
可以: string、number、plain object、array
谨慎: Date、Map、class instance
不行: function、DB connection
这带来新的架构心智:组件不再只是 UI 单元,也带有运行环境属性。团队需要约定哪些组件放服务端,哪些组件放客户端。
七、常见误区与追问
- 误区:RSC 就是 SSR 的新名字。 SSR 是渲染 HTML 的方式,RSC 是组件执行环境模型。
- 误区:Server Component 可以写 onClick。 事件处理需要 Client Component。
- 误区:用了 RSC 就没有 hydration。 Client Component 边界仍需要下载 JS 并 hydrate。
- 追问:RSC 为什么能减少 JS? Server Component 代码和服务端依赖不进入客户端 bundle。
- 追问:
'use client'标记什么? 标记模块进入客户端组件边界,而不是只标记某一行逻辑。 - 追问:RSC 能访问数据库吗? 可以在服务端环境访问,但不要把敏感结果无控制传给客户端。
八、加强记忆
RSC 和 SSR 要用两个维度记:SSR 是“服务端先生成 HTML”,RSC 是“组件可选择只在服务端执行”。Server Component 负责取数据、拼静态结构、减少客户端 JS;Client Component 负责状态、事件和浏览器能力。两者可以组合,不能混为一个概念。