React Server Components(RSC)是React 18引入的服务端渲染机制,允许组件在服务端执行并直接访问数据库和文件系统,将渲染结果以序列化格式流式传输到客户端。与传统的SSR不同,RSC不需要在客户端hydrate,服务端组件的JavaScript代码不会发送到浏览器,从根本上减少了客户端bundle体积。本文从RSC的渲染管线到Next.js App Router中的实际应用,解析服务端组件的工程实践。
React Server Components与SSR的本质区别
传统SSR(getServerSideProps或renderToString)在服务端生成HTML字符串,客户端加载完整JavaScript bundle后执行hydration,将HTML变为可交互的React应用。整个组件树的JavaScript代码必须发送到客户端,bundle体积随组件数量线性增长。
RSC采用不同的思路:组件分为Server Component和Client Component两种。Server Component在服务端执行,渲染结果序列化为RSC Payload(一种JSON格式的流式协议),客户端React运行时接收Payload后直接构建组件树,无需加载服务端组件的代码。只有标记为”use client”的Client Component才会被打包进客户端bundle。
RSC Payload格式示例:
[
[
"$",
"div",
null,
{
"children": [
["$","h1",null,{"children":"文章列表"}],
["$","ul",null,{"children":[
["$","li",null,{"children":[
"$Sreact.fragment",
[{"title":"React RSC指南"}]
]]
}]}]
]
}
]
]
]
其中”$”表示React内置元素,”$Sreact.fragment”表示Fragment。这种格式可以被流式解析——客户端不需要等待整个响应完成,解析到一部分就能渲染一部分。
Next.js App Router中的RSC实践
Next.js 13+的App Router原生支持RSC。app目录下的所有组件默认是Server Component,可以直接进行异步数据获取:
// app/articles/page.tsx
// 默认为Server Component,无需"use client"
interface Article {
id: number;
title: string;
content: string;
author: string;
}
async function getArticles(): Promise<Article[]> {
// 直接在服务端组件中访问数据库
// 无需API层,无需getServerSideProps
const res = await fetch(`${process.env.DB_URL}/articles`, {
next: { revalidate: 60 } // ISR缓存60秒
});
return res.json();
}
export default async function ArticlesPage() {
const articles = await getArticles();
return (
<div>
<h1>文章列表</h1>
<ArticleList articles={articles} />
</div>
);
}
Server Component中可以直接使用async/await获取数据,无需useEffect或getServerSideProps。fetch的next.revalidate选项控制ISR(Incremental Static Regeneration)缓存时间,60秒内复用缓存结果,过期后后台重新生成。
当需要交互逻辑时,在子组件中添加”use client”声明:
// app/articles/LikeButton.tsx
"use client";
import { useState } from 'react';
export default function LikeButton({ articleId }: { articleId: number }) {
const [liked, setLiked] = useState(false);
const [count, setCount] = useState(0);
const handleLike = async () => {
setLiked(!liked);
setCount(prev => liked ? prev - 1 : prev + 1);
await fetch(`/api/articles/${articleId}/like`, {
method: 'POST'
});
};
return (
<button onClick={handleLike} className={liked ? 'liked' : ''}>
{count} 赞
</button>
);
}
Client Component的边界会自动切分——LikeButton的代码进入客户端bundle,但其父级ArticleList仍作为Server Component在服务端渲染。React通过RSC Payload中的引用标记处理Server Component向Client Component传参的序列化。
流式渲染与Suspense边界设计
RSC支持流式渲染,配合Suspense实现渐进式页面加载。慢速数据请求不会阻塞整个页面,先渲染已有内容,待数据就绪后再填充Suspense区域:
// app/dashboard/page.tsx
import { Suspense } from 'react';
import StatsCard from './StatsCard';
import ActivityFeed from './ActivityFeed';
export default function DashboardPage() {
return (
<div>
<h1>数据看板</h1>
<Suspense fallback={<div>加载统计数据...</div>}>
<!-- StatsCard是异步Server Component -->
<StatsCard />
</Suspense>
<Suspense fallback={<div>加载活动流...</div>}>
<ActivityFeed />
</Suspense>
</div>
);
}
// app/dashboard/StatsCard.tsx
async function getStats() {
// 模拟慢查询
const res = await fetch('https://api.example.com/stats', {
cache: 'no-store'
});
return res.json();
}
export default async function StatsCard() {
const stats = await getStats();
return (
<div className="stats-card">
<span>总用户: {stats.totalUsers}</span>
<span>今日活跃: {stats.dailyActive}</span>
</div>
);
}
浏览器接收到的响应流会先包含h1标题和两个Suspense的fallback内容,当StatsCard的数据返回后,React将更新以RSC Payload格式追加到响应流中,客户端React运行时解析并替换对应的Suspense fallback。用户感知到的效果是页面立即展示骨架屏,各模块依次加载完成。
Server与Client Component数据传递约束
Server Component向Client Component传递的props必须可序列化——字符串、数字、布尔值、数组和普通对象可以传递,但函数、Class实例、Symbol等不可序列化的值会报错。
一种常见模式是通过Server Component获取数据,将纯数据传给Client Component:
// app/products/page.tsx (Server Component)
import ProductFilter from './ProductFilter';
export default async function ProductsPage() {
const products = await getProducts();
return <ProductFilter products={products} />;
}
// app/products/ProductFilter.tsx (Client Component)
"use client";
import { useState, useMemo } from 'react';
export default function ProductFilter({ products }: { products: Product[] }) {
const [category, setCategory] = useState('all');
const filtered = useMemo(() => {
if (category === 'all') return products;
return products.filter(p => p.category === category);
}, [products, category]);
return (/* 渲染过滤后的产品列表 */);
}
products数组从Server Component传入,只包含可序列化的数据。过滤逻辑在客户端执行,不触发额外的服务端请求。这种”服务端取数据+客户端交互”的模式兼顾了首屏性能和交互体验。
Bundle分析与会话调试技巧
使用Next.js的bundle分析工具验证RSC是否有效减少了客户端代码体积:
# 安装bundle分析插件
npm install @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
});
module.exports = withBundleAnalyzer({});
# 运行分析
ANALYZE=true npm run build
构建后会生成client和server两份bundle报告。对比发现,Server Component的代码仅出现在server bundle中,client bundle只包含”use client”组件及其依赖。对于内容管理型页面,客户端bundle可从传统SSR的300KB以上降至50KB以内。
React DevTools v5以上版本支持RSC组件树检查,在Components面板中Server Component显示为灰色背景,Client Component显示为白色。Profiler面板可以查看服务端渲染耗时,帮助定位慢速数据获取。
React Server Components通过将数据获取和渲染逻辑留在服务端,从根本上改变了前端应用的性能模型。配合Suspense流式渲染和App Router的路由级缓存策略,RSC在内容密集型应用中可实现接近静态页面的加载速度,同时保留动态数据的实时性。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reactservercomponents-fu-wu-duan-zu-jian-xuan-ran-yuan-li/