React Server Components 深度解析

引言

React Server Components (RSC) 是 React 18 引入的革命性特性,在 React 19 中成为稳定功能。它允许组件在服务器端渲染,减少客户端 JavaScript,提升性能。

官方资料来源:


一、什么是 Server Components

1.1 核心概念

Server Components 是在服务器端执行的 React 组件,它们:

  • 不会发送到客户端:减少 bundle 大小
  • 可以直接访问后端资源:数据库、文件系统、API
  • 支持 async/await:直接在组件中异步获取数据

1.2 Server Components vs Client Components

特性 Server Components Client Components
执行位置 服务器 浏览器
可以使用 Hooks
可以访问后端
可以使用浏览器 API
可以有交互
Bundle 大小 0 计入

二、基础用法

2.1 创建 Server Component

jsx 复制代码
// app/blog/[id]/page.jsx (Server Component)
import { db } from '@/lib/db';

export default async function BlogPost({ params }) {
  // 直接在组件中查询数据库
  const post = await db.posts.findById(params.id);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.author}</p>
      <div>{post.content}</div>
    </article>
  );
}

关键点:

  • 函数是 async
  • 直接使用 await 获取数据
  • 不需要 useEffectuseState

2.2 创建 Client Component

jsx 复制代码
// components/LikeButton.jsx (Client Component)
'use client'; // 标记为 Client Component

import { useState } from 'react';

export default function LikeButton({ postId }) {
  const [likes, setLikes] = useState(0);
  
  const handleLike = async () => {
    await fetch(`/api/like/${postId}`, { method: 'POST' });
    setLikes(likes + 1);
  };
  
  return (
    <button onClick={handleLike}>
      ❤️ {likes}
    </button>
  );
}

关键点:

  • 文件顶部添加 'use client'
  • 可以使用 Hooks 和事件处理

2.3 组合使用

jsx 复制代码
// app/blog/[id]/page.jsx (Server Component)
import { db } from '@/lib/db';
import LikeButton from '@/components/LikeButton'; // Client Component

export default async function BlogPost({ params }) {
  const post = await db.posts.findById(params.id);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      
      {/* Client Component 嵌入 Server Component */}
      <LikeButton postId={post.id} />
    </article>
  );
}

三、数据获取模式

3.1 并行数据获取

jsx 复制代码
// ❌ 串行获取(慢)
export default async function Dashboard() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments(user.id);
  
  return <div>...</div>;
}

// ✅ 并行获取(快)
export default async function Dashboard() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments()
  ]);
  
  return <div>...</div>;
}

3.2 流式渲染(Streaming)

jsx 复制代码
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* 立即显示 */}
      <UserInfo />
      
      {/* 异步加载,显示 loading */}
      <Suspense fallback={<Skeleton />}>
        <Posts />
      </Suspense>
      
      <Suspense fallback={<Skeleton />}>
        <Comments />
      </Suspense>
    </div>
  );
}

async function Posts() {
  const posts = await fetchPosts(); // 慢查询
  return <div>{posts.map(...)}</div>;
}

优势:

  • 页面立即显示
  • 慢查询不阻塞快内容
  • 更好的用户体验

3.3 预加载数据

jsx 复制代码
// app/blog/[id]/page.jsx
import { preload } from '@/lib/data';

export default async function BlogPost({ params }) {
  // 预加载数据(不等待)
  preload(params.id);
  
  // 实际获取数据
  const post = await fetchPost(params.id);
  
  return <article>{post.content}</article>;
}

// lib/data.js
import { cache } from 'react';

export const fetchPost = cache(async (id) => {
  return await db.posts.findById(id);
});

export const preload = (id) => {
  void fetchPost(id); // 触发缓存
};

四、性能优化

4.1 使用 React Cache

jsx 复制代码
import { cache } from 'react';

// 缓存数据库查询
export const getUser = cache(async (id) => {
  return await db.users.findById(id);
});

// 多次调用只执行一次
export default async function Page() {
  const user1 = await getUser(1); // 执行查询
  const user2 = await getUser(1); // 使用缓存
  
  return <div>...</div>;
}

4.2 代码分割

jsx 复制代码
// Server Component 自动代码分割
import HeavyComponent from './HeavyComponent'; // 不会打包到客户端

export default async function Page() {
  const data = await fetchData();
  
  // HeavyComponent 只在服务器执行
  return <HeavyComponent data={data} />;
}

4.3 减少客户端 JavaScript

jsx 复制代码
// ❌ 不好:整个库打包到客户端
'use client';
import { format } from 'date-fns';

export default function Post({ date }) {
  return <time>{format(date, 'PPP')}</time>;
}

// ✅ 好:在服务器格式化
import { format } from 'date-fns';

export default function Post({ date }) {
  const formatted = format(date, 'PPP');
  return <time>{formatted}</time>;
}

五、常见模式

5.1 布局组件

jsx 复制代码
// app/layout.jsx (Server Component)
export default async function RootLayout({ children }) {
  const user = await getCurrentUser();
  
  return (
    <html>
      <body>
        <Header user={user} />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

5.2 数据提供者模式

jsx 复制代码
// app/dashboard/layout.jsx
export default async function DashboardLayout({ children }) {
  const user = await getUser();
  
  return (
    <div>
      <Sidebar user={user} />
      <main>{children}</main>
    </div>
  );
}

// app/dashboard/page.jsx
export default async function DashboardPage() {
  // 不需要重复获取 user,layout 已经获取了
  const stats = await getStats();
  
  return <div>{stats}</div>;
}

5.3 条件渲染

jsx 复制代码
export default async function Page() {
  const user = await getUser();
  
  if (!user) {
    return <LoginPrompt />;
  }
  
  if (!user.isPremium) {
    return <UpgradePrompt />;
  }
  
  return <PremiumContent />;
}

六、最佳实践

6.1 何时使用 Server Components

适合:

  • 数据获取
  • 访问后端资源
  • 敏感信息处理(API keys)
  • 大型依赖库(只在服务器使用)

不适合:

  • 需要交互(onClick, onChange)
  • 需要浏览器 API(localStorage, window)
  • 需要 Hooks(useState, useEffect)

6.2 何时使用 Client Components

适合:

  • 交互式 UI(按钮、表单)
  • 使用浏览器 API
  • 使用 React Hooks
  • 第三方库需要客户端

6.3 组件边界

jsx 复制代码
// ❌ 不好:整个页面都是 Client Component
'use client';

export default function Page() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <Header /> {/* 不需要交互,但被迫成为 Client Component */}
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <Footer /> {/* 不需要交互,但被迫成为 Client Component */}
    </div>
  );
}

// ✅ 好:只有需要交互的部分是 Client Component
export default function Page() {
  return (
    <div>
      <Header /> {/* Server Component */}
      <Counter /> {/* Client Component */}
      <Footer /> {/* Server Component */}
    </div>
  );
}

// components/Counter.jsx
'use client';
export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

七、实战案例

7.1 博客系统

jsx 复制代码
// app/blog/[slug]/page.jsx
import { db } from '@/lib/db';
import { Suspense } from 'react';
import CommentList from '@/components/CommentList';
import CommentForm from '@/components/CommentForm';

export default async function BlogPost({ params }) {
  // 并行获取数据
  const [post, author] = await Promise.all([
    db.posts.findBySlug(params.slug),
    db.users.findById(post.authorId)
  ]);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>作者:{author.name}</p>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      
      {/* 评论列表(流式加载) */}
      <Suspense fallback={<div>加载评论中...</div>}>
        <CommentList postId={post.id} />
      </Suspense>
      
      {/* 评论表单(Client Component) */}
      <CommentForm postId={post.id} />
    </article>
  );
}

// 生成静态路径
export async function generateStaticParams() {
  const posts = await db.posts.findAll();
  return posts.map(post => ({ slug: post.slug }));
}

7.2 电商产品页

jsx 复制代码
// app/products/[id]/page.jsx
export default async function ProductPage({ params }) {
  const product = await fetchProduct(params.id);
  
  return (
    <div>
      <ProductImages images={product.images} /> {/* Client Component */}
      <ProductInfo product={product} /> {/* Server Component */}
      
      <Suspense fallback={<Skeleton />}>
        <RelatedProducts categoryId={product.categoryId} />
      </Suspense>
      
      <Suspense fallback={<Skeleton />}>
        <Reviews productId={product.id} />
      </Suspense>
    </div>
  );
}

八、常见问题

Q1: Server Components 可以导入 Client Components 吗?

✅ 可以

jsx 复制代码
// Server Component
import ClientButton from './ClientButton'; // ✅

export default function Page() {
  return <ClientButton />;
}

Q2: Client Components 可以导入 Server Components 吗?

❌ 不可以直接导入,但可以通过 children 传递

jsx 复制代码
// ❌ 不行
'use client';
import ServerComponent from './ServerComponent';

export default function ClientComponent() {
  return <ServerComponent />; // 错误
}

// ✅ 可以
'use client';
export default function ClientComponent({ children }) {
  return <div>{children}</div>;
}

// 使用
<ClientComponent>
  <ServerComponent /> {/* 作为 children 传递 */}
</ClientComponent>

Q3: 如何在 Server Component 中使用环境变量?

jsx 复制代码
// ✅ 直接使用(不会暴露给客户端)
export default async function Page() {
  const apiKey = process.env.SECRET_API_KEY;
  const data = await fetch(`https://api.example.com?key=${apiKey}`);
  
  return <div>{data}</div>;
}

总结

React Server Components 的核心优势:

  1. 性能提升

    • 减少客户端 JavaScript
    • 更快的首屏加载
    • 自动代码分割
  2. 开发体验

    • 直接访问后端资源
    • 简化数据获取
    • 更好的类型安全
  3. 最佳实践

    • Server Components 处理数据
    • Client Components 处理交互
    • 合理划分组件边界

参考资料:


相关推荐
wordbaby2 小时前
一次跨端 Loading 卡死复盘:把请求计数从 Axios 拦截器迁到 try/catch/finally
前端·axios
我命由我123452 小时前
JavaScript 开发 - 获取函数名称、获取函数参数数量、获取函数参数名称
开发语言·前端·javascript·css·html·html5·js
IT_陈寒3 小时前
JavaScript里这个隐式类型转换的坑,我终于爬出来了
前端·人工智能·后端
方呵呵3 小时前
一个 3.5k Star Vue H5 项目的二次进化:我把它重构成了 Monorepo 工程体系
前端
_风满楼3 小时前
HTTP 请求的五种传参方式
前端·javascript·后端
木斯佳3 小时前
前端八股文面经大全:字节暑期前端一面(2026-04-22)·面经深度解析
前端
光影少年3 小时前
前端线上屏幕出现卡顿如何排查?
开发语言·前端·javascript·学习·前端框架·node.js
Yeh2020583 小时前
request与response笔记
java·前端·笔记
像我这样帅的人丶你还4 小时前
前端监控体系与实践:从错误上报到内存与 GC 观测
前端·javascript·架构