Skip to content

Server Components概念

学习目标

通过本章学习,你将掌握:

  • Server Components的核心概念
  • 服务端渲染的演进历史
  • Server Components的工作原理
  • 与传统SSR的区别
  • Server Components的优势
  • React 19中的Server Components
  • 适用场景分析
  • 架构设计思想

第一部分:什么是Server Components

1.1 基本概念

Server Components(服务器组件)是React 19引入的革命性特性,组件在服务器端渲染并流式传输到客户端,不会包含在客户端JavaScript bundle中

jsx
// 这是一个Server Component(默认)
async function BlogPost({ id }) {
  // 在服务器端执行
  const post = await fetchPost(id);  // 直接await,无需use()
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// 客户端收到的是HTML,不是JavaScript

1.2 核心特点

1. 零客户端JavaScript

jsx
// Server Component
async function UserList() {
  const users = await db.users.findMany();
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// 打包后:
// ✅ 服务器代码:包含数据库查询
// ✅ 客户端bundle:0 KB(不包含这个组件)
// ✅ 传输内容:只有HTML

2. 直接访问服务器资源

jsx
import { readFile } from 'fs/promises';
import { db } from '@/lib/database';
import { redis } from '@/lib/cache';

// Server Component可以直接使用Node.js API
async function ServerOnlyComponent() {
  // 直接读取文件
  const config = await readFile('./config.json', 'utf-8');
  
  // 直接查询数据库
  const users = await db.query('SELECT * FROM users');
  
  // 直接访问缓存
  const cached = await redis.get('data');
  
  return <div>{/* 渲染数据 */}</div>;
}

// 这些代码永远不会发送到客户端!

3. 自动代码分割

jsx
// Server Component
import HeavyLibrary from 'heavy-library';  // 1MB的库

async function DataProcessor() {
  const data = await fetchData();
  
  // 使用大型库处理数据
  const processed = HeavyLibrary.process(data);
  
  return <div>{processed}</div>;
}

// heavy-library不会包含在客户端bundle中!
// 只在服务器执行

1.3 与Client Components的对比

jsx
// ========== Server Component(默认) ==========
// 文件:app/ServerComponent.jsx
async function ServerComponent() {
  const data = await fetchData();
  return <div>{data}</div>;
}

// 特点:
// - 在服务器运行
// - 可以async/await
// - 不能使用Hooks
// - 不能使用浏览器API
// - 不包含在客户端bundle


// ========== Client Component ==========
// 文件:app/ClientComponent.jsx
'use client';  // 必须声明

function ClientComponent() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      点击: {count}
    </button>
  );
}

// 特点:
// - 在客户端运行
// - 可以使用Hooks
// - 可以使用浏览器API
// - 包含在客户端bundle
// - 不能async/await(组件函数)

第二部分:渲染历史演进

2.1 CSR(客户端渲染)

jsx
// 传统React应用(CSR)
function App() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []);
  
  if (!data) return <div>Loading...</div>;
  
  return <div>{data.content}</div>;
}

// 流程:
// 1. 下载HTML(几乎为空)
// 2. 下载JavaScript bundle
// 3. 执行React
// 4. 发起API请求
// 5. 显示内容

// 问题:
// ❌ 白屏时间长
// ❌ SEO不友好
// ❌ 首次加载慢

2.2 SSR(服务端渲染)

jsx
// Next.js传统SSR
export async function getServerSideProps() {
  const data = await fetchData();
  
  return {
    props: { data }
  };
}

function Page({ data }) {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <div>{data.content}</div>
      <button onClick={() => setCount(count + 1)}>
        {count}
      </button>
    </div>
  );
}

// 流程:
// 1. 服务器渲染HTML
// 2. 发送HTML到客户端(可见但不可交互)
// 3. 下载JavaScript bundle
// 4. Hydration(激活交互)
// 5. 完全可交互

// 问题:
// ❌ 全页面hydration(整个组件树都需要JS)
// ❌ 客户端bundle仍然很大
// ❌ 数据获取和渲染分离

2.3 Server Components(React 19)

jsx
// Server Component(无需hydration)
async function BlogPost({ id }) {
  // 在服务器直接获取数据
  const post = await db.posts.findById(id);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      
      {/* 嵌入Client Component实现交互 */}
      <LikeButton postId={post.id} />
    </article>
  );
}

// Client Component(只有这部分需要JS)
'use client';

function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
  
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}

// 流程:
// 1. 服务器渲染Server Component
// 2. 流式传输HTML
// 3. 只下载Client Components的JS
// 4. 只hydrate Client Components
// 5. 完全可交互

// 优势:
// ✅ 零客户端JS(Server Components部分)
// ✅ 流式渲染
// ✅ 自动代码分割
// ✅ 选择性hydration

第三部分:工作原理

3.1 组件树分析

jsx
// 应用结构
<Page>                    {/* Server Component */}
  <Header />              {/* Server Component */}
  <Sidebar />             {/* Server Component */}
  <MainContent>           {/* Server Component */}
    <Post />              {/* Server Component */}
    <LikeButton />        {/* Client Component */}
    <Comments />          {/* Server Component */}
    <CommentForm />       {/* Client Component */}
  </MainContent>
  <Footer />              {/* Server Component */}
</Page>

// 渲染过程:
// 1. 服务器渲染所有Server Components
// 2. 生成HTML + Client Components占位符
// 3. 流式传输到客户端
// 4. 客户端只加载Client Components的JS
// 5. 只hydrate Client Components

3.2 序列化格式

Server Components使用特殊的序列化格式(RSC Payload)传输到客户端:

javascript
// Server Component返回的不是HTML,而是序列化的React元素树
{
  "type": "div",
  "props": {
    "children": [
      {
        "type": "h1",
        "props": { "children": "Hello" }
      },
      {
        "type": "@client/LikeButton",  // Client Component引用
        "props": { "postId": 123 }
      }
    ]
  }
}

// 客户端React接收这个结构并渲染
// Client Components会被替换为实际的组件代码

3.3 数据流

jsx
// 数据流动方向
┌──────────────┐
│   数据库      │
└──────┬───────┘


┌──────────────┐
│Server Comp   │  ← 在服务器获取数据
└──────┬───────┘

       ↓ (序列化)
┌──────────────┐
│  网络传输     │
└──────┬───────┘


┌──────────────┐
│  客户端React  │  ← 渲染Server Component的输出
└──────┬───────┘      + hydrate Client Components


┌──────────────┐
│   浏览器DOM   │
└──────────────┘

// Server Component的数据获取在服务器完成
// 客户端只接收渲染结果

3.4 刷新机制

jsx
// Server Components可以重新获取数据而无需完整页面刷新
'use client';

function RefreshButton() {
  const router = useRouter();
  
  const handleRefresh = () => {
    // 触发Server Components重新渲染
    router.refresh();
  };
  
  return (
    <button onClick={handleRefresh}>
      刷新数据
    </button>
  );
}

// 流程:
// 1. 用户点击刷新
// 2. 向服务器请求新的RSC Payload
// 3. Server Components重新执行(重新获取数据)
// 4. 客户端接收新的RSC Payload
// 5. React更新对应的DOM
// 6. Client Components的状态保持不变!

第四部分:优势分析

4.1 性能优势

1. 更小的bundle

jsx
// 传统方式
import { format } from 'date-fns';  // 67 KB
import { marked } from 'marked';     // 45 KB
import hljs from 'highlight.js';    // 85 KB

function BlogPost({ post }) {
  const formatted = format(new Date(post.date), 'PPP');
  const html = marked(post.content);
  const highlighted = hljs.highlightAuto(html);
  
  return <div dangerouslySetInnerHTML={{ __html: highlighted }} />;
}

// 客户端bundle: +197 KB


// Server Components方式
import { format } from 'date-fns';  // 只在服务器
import { marked } from 'marked';
import hljs from 'highlight.js';

async function BlogPost({ id }) {
  const post = await fetchPost(id);
  const formatted = format(new Date(post.date), 'PPP');
  const html = marked(post.content);
  const highlighted = hljs.highlightAuto(html);
  
  return <div dangerouslySetInnerHTML={{ __html: highlighted }} />;
}

// 客户端bundle: +0 KB!

2. 更快的首屏

jsx
// 数据获取在服务器完成,无需等待客户端JavaScript
async function ProductPage({ id }) {
  // 并行获取所有数据
  const [product, reviews, recommendations] = await Promise.all([
    fetchProduct(id),
    fetchReviews(id),
    fetchRecommendations(id)
  ]);
  
  return (
    <div>
      <ProductDetail product={product} />
      <Reviews reviews={reviews} />
      <Recommendations items={recommendations} />
    </div>
  );
}

// 用户看到的:
// ✅ 立即显示完整内容(已经在服务器渲染)
// ✅ 无需等待JavaScript下载
// ✅ 无需等待客户端数据获取

3. 流式渲染

jsx
// Server Components支持Suspense和流式传输
<Suspense fallback={<HeaderSkeleton />}>
  <Header />
</Suspense>

<Suspense fallback={<ContentSkeleton />}>
  <MainContent />
</Suspense>

<Suspense fallback={<SidebarSkeleton />}>
  <Sidebar />
</Suspense>

// 渲染流程:
// 1. 立即发送页面shell(骨架屏)
// 2. Header ready → 流式替换HeaderSkeleton
// 3. MainContent ready → 流式替换ContentSkeleton
// 4. Sidebar ready → 流式替换SidebarSkeleton

// 用户体验:渐进式显示内容,无需等待全部完成

4.2 开发体验优势

1. 简化数据获取

jsx
// 传统方式:复杂的数据获取逻辑
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    Promise.all([
      fetchUser(userId),
      fetchPosts(userId)
    ]).then(([userData, postsData]) => {
      setUser(userData);
      setPosts(postsData);
      setLoading(false);
    });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  
  return <div>...</div>;
}

// Server Components:直观简洁
async function UserProfile({ userId }) {
  const [user, posts] = await Promise.all([
    fetchUser(userId),
    fetchPosts(userId)
  ]);
  
  return <div>...</div>;
}

2. 直接访问后端

jsx
// Server Component可以直接导入服务器代码
import { db } from '@/lib/database';
import { getCurrentUser } from '@/lib/auth';
import { sendEmail } from '@/lib/email';

async function Dashboard() {
  // 直接访问数据库
  const user = await getCurrentUser();
  const stats = await db.stats.getUserStats(user.id);
  
  // 直接发送邮件
  if (stats.needsNotification) {
    await sendEmail(user.email, 'Notification');
  }
  
  return <div>{/* 渲染数据 */}</div>;
}

// 无需创建API路由!
// 无需担心暴露敏感代码!

3. 自动优化

jsx
// React自动优化Server Components
async function OptimizedComponent() {
  const data = await fetchData();
  
  return (
    <div>
      {/* React可以分析并优化这个组件 */}
      {/* 自动代码分割、tree shaking等 */}
      {data.items.map(item => (
        <ItemCard key={item.id} item={item} />
      ))}
    </div>
  );
}

4.3 安全优势

jsx
// 敏感代码只在服务器执行
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/database';

async function PaymentPage({ orderId }) {
  // API密钥、数据库连接等敏感信息
  // 永远不会暴露到客户端
  const order = await db.orders.findById(orderId);
  const paymentIntent = await stripe.paymentIntents.create({
    amount: order.total,
    currency: 'usd'
  });
  
  return (
    <div>
      <OrderSummary order={order} />
      <PaymentForm clientSecret={paymentIntent.client_secret} />
    </div>
  );
}

// stripe密钥、数据库连接等
// 完全不会包含在客户端代码中

第五部分:适用场景

5.1 适合Server Components

jsx
// ✅ 数据展示组件
async function ProductList({ category }) {
  const products = await db.products.findByCategory(category);
  return <ProductGrid products={products} />;
}

// ✅ 静态内容
async function BlogPost({ slug }) {
  const post = await getPostBySlug(slug);
  return <Article content={post.content} />;
}

// ✅ 服务器数据处理
async function Analytics() {
  const stats = await calculateStats();
  const reports = await generateReports(stats);
  return <Dashboard data={reports} />;
}

// ✅ SEO关键内容
async function ProductPage({ id }) {
  const product = await fetchProduct(id);
  return (
    <>
      <title>{product.name}</title>
      <meta name="description" content={product.description} />
      <ProductDetail product={product} />
    </>
  );
}

5.2 需要Client Components

jsx
// ✅ 交互式组件
'use client';

function InteractiveChart({ data }) {
  const [selected, setSelected] = useState(null);
  
  return (
    <Chart 
      data={data}
      onSelect={setSelected}
    />
  );
}

// ✅ 使用浏览器API
'use client';

function GeolocationComponent() {
  const [location, setLocation] = useState(null);
  
  useEffect(() => {
    navigator.geolocation.getCurrentPosition(pos => {
      setLocation(pos.coords);
    });
  }, []);
  
  return <Map location={location} />;
}

// ✅ 使用Hooks
'use client';

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ✅ 事件处理
'use client';

function Form() {
  const handleSubmit = (e) => {
    e.preventDefault();
    // 处理提交
  };
  
  return <form onSubmit={handleSubmit}>...</form>;
}

注意事项

1. Server Components的限制

jsx
// ❌ 不能使用Hooks
async function BadServerComponent() {
  const [state, setState] = useState(0);  // 错误!
  return <div>{state}</div>;
}

// ❌ 不能使用浏览器API
async function BadServerComponent() {
  const width = window.innerWidth;  // 错误!window未定义
  return <div>{width}</div>;
}

// ❌ 不能使用事件处理
async function BadServerComponent() {
  return (
    <button onClick={() => alert('hi')}>  {/* 错误!*/}
      Click
    </button>
  );
}

2. 组件边界清晰

jsx
// ✅ 明确区分Server和Client Components
// server-component.jsx
async function ServerComponent() {
  const data = await fetchData();
  return <ClientComponent data={data} />;
}

// client-component.jsx
'use client';

function ClientComponent({ data }) {
  const [selected, setSelected] = useState(null);
  return <div>{/* 使用data和state */}</div>;
}

3. 数据序列化

jsx
// ❌ 不能传递函数
async function ServerComponent() {
  const handleClick = () => console.log('click');
  
  return <ClientComponent onClick={handleClick} />;  // 错误!
}

// ✅ 传递可序列化的数据
async function ServerComponent() {
  const data = { id: 1, name: 'Test' };
  
  return <ClientComponent data={data} />;  // 正确
}

常见问题

Q1: Server Components会替代传统SSR吗?

A: 不是替代,而是增强。Server Components与SSR结合使用,提供更好的性能和开发体验。

Q2: 所有组件都应该是Server Components吗?

A: 不是。需要交互的部分应该是Client Components。

Q3: Server Components如何更新数据?

A: 通过路由刷新或Server Actions触发重新渲染。

总结

Server Components的核心价值

✅ 零客户端JavaScript
✅ 直接访问服务器资源
✅ 自动代码分割
✅ 更小的bundle
✅ 更快的首屏
✅ 更好的SEO
✅ 流式渲染
✅ 简化数据获取

架构思想

传统React: 一切都在客户端
SSR: 服务器渲染 + 客户端hydration
Server Components: 服务器组件 + 选择性客户端组件

核心理念:
- 默认服务器渲染
- 按需客户端交互
- 最小化JavaScript传输
- 最大化性能

Server Components是React 19最重要的创新,代表了Web开发的新方向!