RSC 常被当成「服务端渲染的升级版」,这个理解容易走偏。它真正解决的问题是:组件对数据的依赖,不必再以 JavaScript 的形式发送到浏览器。
一、先看没有 RSC 时的困境
一个典型的列表页要做四件事:请求数据、格式化、渲染、绑定交互。前三件在服务端就能完成,第四件不行。但在 RSC 之前,这三件事的代码都会进入客户端 bundle。
tsx
// 这段代码里,真正需要跑在浏览器里的只有 onClick
export function PostList({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map((post) => (
<li key={post.slug} onClick={() => track(post.slug)}>
{formatDate(post.date)} · {post.title}
</li>
))}
</ul>
);
}formatDate 和整个日期库,都被迫跟过去。
二、RSC 的切分方式
RSC 引入了第三种组件类型。区别不在「跑在哪」,而在是否参与客户端运行时:
| 服务端组件 | 客户端组件 | |
|---|---|---|
| 能否直接读文件 / 数据库 | 能 | 不能 |
| 是否进入客户端 bundle | 否 | 是 |
| 能否使用 useState | 不能 | 能 |
| 能否 import 另一类 | 只能 import 客户端组件的引用 | 可 import 服务端组件(作为 children) |
关键约束是最后一行:服务端组件可以渲染客户端组件,反过来不行——但可以通过 children 把服务端内容「传送」进客户端组件。
三、什么时候不该用
RSC 不是免费的。它有两个明确成本:
- 交互密度高的界面会变得别扭。表单、拖拽、实时预览这类,边界会切得很碎。
- 调试链路变长。一个报错可能同时在服务端和客户端出现,堆栈方向相反。
我的判断标准很粗暴:如果一个组件的 props 里全是回调函数,它就该是客户端组件,别硬拆。
四、结论
RSC 的价值不在性能数字,而在它把「数据获取」和「交互」这两件性质不同的事,在语言层面分开了。分开之后,静态站点能做的事情多了一大块——比如这个博客,全部文章在构建期就渲染成了 HTML,浏览器里几乎没有多余的计算。