学习路径 高级

React Server Components

学习 React Server Components (RSC),理解服务端渲染的新范式

Server Components 概述

React Server Components (RSC) 是 React 18 中引入的实验性特性,在 React 19 中正式稳定。它允许组件在服务端渲染,实现更好的性能和开发体验。

核心优势

  • 零 Bundle 大小:服务端代码不发送到客户端
  • 直接访问后端:可以直接查询数据库、文件系统
  • 自动代码分割:无需手动指定加载边界
  • 避免 Client-Server Waterfalls:减少数据获取往返
  • 流式渲染:渐进式发送 HTML
RSC vs SSR

Server-Side Rendering (SSR):服务端生成完整的 HTML,然后在客户端激活(hydrate)。

React Server Components (RSC):组件在服务端渲染,可以选择性在客户端交互,支持流式渲染和零打包大小。

Server vs Client Components

Server Components (服务端组件)

默认情况下,Next.js App Router 中的所有组件都是 Server Components。

TypeScript
// Server Component (默认)
// ✅ 可以直接在服务端运行

async function BlogPosts() {
  const posts = await db.query('SELECT * FROM posts');  // 直接查询数据库

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

Client Components (客户端组件)

需要使用 'use client' 指令标记。

TypeScript
'use client';

import { useState } from 'react';

function LikeButton() {
  const [likes, setLikes] = useState(0);

  return (
    <button onClick={() => setLikes(likes + 1)}>
      ❤️ {likes}
    </button>
  );
}

对比表

特性Server ComponentsClient Components
指令无 (默认)'use client'
Bundle 大小0 KB增加到客户端 bundle
访问后端资源✅ 直接访问❌ 需要 API
使用 Hooks❌ 仅部分 Server Hooks✅ 所有 Hooks
浏览器 API❌ 不可用✅ 完全可用
事件处理❌ 不可用✅ 完全可用

何时使用 Server Components

✅ 使用 Server Components

  • 数据获取:从数据库或 API 获取数据
  • 静态内容:不需要交互的内容
  • 布局组件:页面整体布局
  • 敏感数据:不想暴露给客户端的逻辑
  • 大型依赖:使用重型库但不需要客户端
TypeScript
// ✅ 好的 Server Component 示例

// 1. 数据获取
async function UserList() {
  const users = await fetchUsers();  // 直接从数据库获取

  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

// 2. 使用大型依赖
import { Markdown } from './markdown-lib';  // 100KB+

function BlogPost({ content }) {
  // 这个库不会发送到客户端!
  return <Markdown>{content}</Markdown>;
}

// 3. 静态布局
function Layout({ children }) {
  return (
    <html>
      <body>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

✅ 使用 Client Components

  • 交互:onClick、onChange 等事件
  • 状态:useState、useReducer 等
  • 浏览器 API:window、document、localStorage
  • 自定义 Hooks:使用了 useState 等的 Hooks
  • React Hooks:useEffect、useLayoutEffect 等
TypeScript
// ✅ 好的 Client Component 示例

'use client';

import { useState, useEffect } from 'react';

function SearchBar() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  // 使用 useEffect (客户端专用)
  useEffect(() => {
    if (query.length > 2) {
      search(query).then(setResults);
    }
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.title}</li>
        ))}
      </ul>
    </div>
  );
}

Server 和 Client 组件互操作

Client 导入 Server

允许:Client 组件可以导入和使用 Server 组件

TypeScript
// ClientComponent.tsx
'use client';

import ServerComponent from './ServerComponent';

function ClientComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        点击 {count} 次
      </button>

      {/* ✅ Client 组件可以使用 Server 组件 */}
      <ServerComponent />
    </div>
  );
}

// ServerComponent.tsx
export default async function ServerComponent() {
  const data = await fetchData();

  return <div>{data.title}</div>;
}

Server 导入 Client

允许:Server 组件可以导入 Client 组件

TypeScript
// Server Component
import ClientComponent from './ClientComponent';

export default async function ServerComponent() {
  const data = await fetchData();

  return (
    <div>
      <h1>{data.title}</h1>

      {/* ✅ Server 组件可以使用 Client 组件 */}
      <ClientComponent initialData={data} />
    </div>
  );
}

通过 Props 传递 Server 组件

TypeScript
// ✅ 作为 props 传递
'use client';

import { useState } from 'react';

function Tabs({ children }: { children: React.ReactNode }) {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <div>
      {children[activeTab]}
    </div>
  );
}

// Server Component
async function Page() {
  const data = await fetchTabData();

  return (
    <Tabs>
      <TabContent />  {/* Server Component */}
      <AnotherTab /> {/* Server Component */}
    </Tabs>
  );
}
限制

不允许:Client 组件导入 Server 组件后,再将 Server 组件作为 props 传递给其他 Client 组件。

TypeScript
// ❌ 错误示例
'use client';

import ServerComponent from './ServerComponent';

function ClientComponent({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}

// Server Component
function Page() {
  return (
    <ClientComponent>
      <ServerComponent />  {/* ❌ 错误! */}
    </ClientComponent>
  );
}

Server Components 数据获取

async / await 组件

TypeScript
async function UserProfile({ userId }: { userId: string }) {
  // 直接在组件中 await
  const user = await db.query(
    'SELECT * FROM users WHERE id = ?',
    [userId]
  );

  const posts = await db.query(
    'SELECT * FROM posts WHERE userId = ?',
    [userId]
  );

  return (
    <div>
      <h1>{user.name}</h1>
      <h2>文章列表</h2>
      {posts.map(post => (
        <article key={post.id}>{post.title}</article>
      ))}
    </div>
  );
}

并行数据获取

TypeScript
async function Dashboard() {
  // ✅ 并行获取数据
  const [users, posts, comments] = await Promise.all([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);

  return (
    <div>
      <UserStats users={users} />
      <PostStats posts={posts} />
      <CommentStats comments={comments} />
    </div>
  );
}

流式渲染 Suspense

TypeScript
import { Suspense } from 'react';

async function Page() {
  return (
    <div>
      <h1>用户资料</h1>

      {/* 立即显示这部分 */}
      <Header />

      {/* 流式加载:先显示骨架,数据到了再显示内容 */}
      <Suspense fallback={<div>加载文章中...</div>}>
        <Posts userId="1" />
      </Suspense>

      <Suspense fallback={<div>加载评论中...</div>}>
        <Comments postId="1" />
      </Suspense>
    </div>
  );
}

async function Posts({ userId }: { userId: string }) {
  const posts = await fetchPosts(userId);
  return <PostList posts={posts} />;
}

使用第三方库

UI 库使用 Client Components

TypeScript
// 许多 UI 库需要 'use client'
'use client';

import { Carousel } from 'some-ui-library';

function Gallery() {
  return (
    <Carousel>
      <img src="/img1.jpg" alt="1" />
      <img src="/img2.jpg" alt="2" />
    </Carousel>
  );
}

// Server Component 中使用
import Gallery from './Gallery';

function Page() {
  return (
    <div>
      <h1>图片画廊</h1>
      <Gallery />
    </div>
  );
}

工具库可以在 Server 使用

TypeScript
// ✅ 可以在 Server Components 使用
import { format } from 'date-fns';  // 纯函数
import { escape } from 'lodash/string';

function Post({ post }: { post: Post }) {
  return (
    <article>
      <time>{format(post.date, 'PPP')}</time>
      <div dangerouslySetInnerHTML={{ __html: escape(post.content) }} />
    </article>
  );
}

Server Components 最佳实践

  1. 默认使用 Server Components

    • 只在需要交互时才添加 'use client'。Server Components 性能更好。
  2. 将交互部分下沉到叶子组件

TypeScript
// ✅ 好:大部分是 Server,只有交互部分是 Client
async function Page() {
  const data = await fetchData();

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
      <LikeButton id={data.id} />  {/* 小的 Client Component */}
    </div>
  );
}

// ❌ 不好:整个页面都是 Client
'use client';
async function Page() {
  // ...
}
  1. Server Components 尽可能靠近数据源

    • 直接在 Server Components 中查询数据库,避免不必要的 API 层。
  2. 使用 Server Actions 处理表单

TypeScript
// Server Action
async function updateProfile(formData: FormData) {
  const name = formData.get('name');
  await db.users.update({ name });
}

// 在 Server Component 中使用
<form action={updateProfile}>
  <input name="name" />
  <button type="submit">更新</button>
</form>
  1. 避免在 Server Components 中使用动态导入
    • 使用静态导入,Next.js 会自动代码分割。

从 Pages Router 迁移到 App Router

getServerSideProps → async Server Components

TypeScript
// ❌ Pages Router (旧方式)
export default function Page({ data }) {
  return <div>{data.name}</div>;
}

export async function getServerSideProps() {
  const data = await fetchData();
  return { props: { data } };
}

// ✅ App Router (新方式)
async function Page() {
  const data = await fetchData();
  return <div>{data.name}</div>;
}

getStaticProps → async Server Components

TypeScript
// ❌ Pages Router
export async function getStaticProps() {
  const data = await fetchData();
  return { props: { data } };
}

// ✅ App Router
async function Page() {
  const data = await fetchData();
  return <div>{data.name}</div>;
}
迁移提示
  • 删除 getServerSideProps/getStaticProps,直接在组件中 async / await
  • 添加 'use client' 到需要状态的组件
  • 将 layout.tsx 和 page.tsx 默认作为 Server Components
  • 使用文件系统路由代替动态路由 API

常见问题

如何调用客户端函数?

使用 Server Actions 或创建 API 路由。

如何使用 Context?

Context 只能在 Client Components 中使用。创建一个 Client Provider 组件,然后在 Server Components 中渲染它。

如何使用 useEffect?

useEffect 只能在 Client Components 中使用。添加 'use client' 指令。

如何调试 Server Components?

使用 console.log (在服务端终端输出) 或使用 React DevTools。

相关资源

这篇文章有帮助吗?