React Server Components(RSC)实战指南:从原理到落地
本文最后更新于235 天前,其中的信息可能已经过时,如有错误请发送邮件到109484028@qq.com

React Server Components(简称 RSC)是 React 团队推出的革命性技术,核心目标是拆分客户端与服务器的组件职责—— 让服务器承担数据获取、 heavy 计算等工作,客户端专注于交互逻辑,最终实现 “更小的 JS 包体积、更快的首屏加载、更优的性能体验”。本文将从基础概念出发,结合实战代码,带你掌握 RSC 的核心用法与最佳实践。

一、RSC 核心概念:先搞懂 “为什么需要它”

在传统 React 应用中,所有组件都运行在客户端,存在两个核心痛点:

  1. 数据获取链路长:客户端需先加载 JS 包,再发起请求获取数据,首屏等待时间长;

  2. JS 包体积膨胀:第三方库(如数据请求、格式化工具)全量打包到客户端,影响加载速度。

RSC 通过 “服务器组件在服务端运行、仅将渲染结果(而非代码)发送到客户端” 解决这些问题,同时保留客户端组件的交互能力。

1. 什么是 React Server Components?

服务器组件(Server Components)是仅在服务端执行的 React 组件,具备以下特性:

  • 无客户端 JS 发送:组件代码不打包到客户端,仅传递渲染后的 HTML 或 React 元素树;

  • 支持异步数据获取:可直接在组件内写 async/await,无需依赖 useEffect 或第三方数据库;

  • 无浏览器 API 限制:可直接操作服务端资源(如数据库、文件系统),无需担心客户端兼容性。

实战代码:一个基础的服务器组件

// app/components/PostList.server.tsx(命名建议加 .server 区分)

// 无需 'use client',默认是服务器组件

import PostCard from './PostCard'; // 若 PostCard 是服务器组件,可直接引入

// 支持 async 函数,直接在组件内获取数据

async function PostList() {

  // 服务端直接请求数据(无需跨域,无需客户端发起)

  const res = await fetch('https://api.your-blog.com/posts?limit=10', {

    cache: 'force-cache', // 服务端缓存策略

  });

  const posts = await res.json();

  return (

&#x20;   \<div className="post-list">

&#x20;     {posts.map((post) => (

&#x20;       \<PostCard key={post.id} post={post} />

&#x20;     ))}

&#x20;   \</div>

&#x20; );

}

export default PostList;

2. 客户端组件 vs 服务器组件:核心区别

RSC 的关键是 “明确组件边界”—— 哪些放服务端,哪些放客户端。两者的核心区别如下表:

特性 服务器组件(Server Components) 客户端组件(Client Components)
运行环境 服务端 客户端(浏览器 / Node.js)
代码是否发送到客户端 否(仅传渲染结果) 是(需打包到 JS 包)
支持异步数据获取 是(直接 async/await) 否(需用 useEffect/SWR/React Query)
支持交互能力 否(无状态、无事件绑定) 是(可使用 useState/useCallback 等 Hooks)
依赖引入 不增加客户端包体积 依赖会打包到客户端

实战代码:客户端组件与服务器组件的配合

客户端组件需用 'use client' 声明,负责交互逻辑;服务器组件负责数据获取与静态渲染,两者可嵌套使用:

// 1. 客户端组件:负责交互(计数器)

// app/components/Counter.client.tsx(命名建议加 .client 区分)

'use client'; // 必须声明,标记为客户端组件

import { useState } from 'react';

export default function Counter() {

&#x20; const \[count, setCount] = useState(0); // 仅客户端组件支持 Hooks

&#x20; return (

&#x20;   \<button&#x20;

&#x20;     onClick={() => setCount(count + 1)}&#x20;

&#x20;     className="counter-btn"

&#x20;   \>

&#x20;     点击次数:{count}

&#x20;   \</button>

&#x20; );

}

// 2. 服务器组件:负责数据获取,嵌套客户端组件

// app/page.tsx(Next.js 13+ App Router 中,默认是服务器组件)

import PostList from './components/PostList.server';

import Counter from './components/Counter.client';

// 服务器组件支持 async 数据获取

async function HomePage() {

&#x20; // 服务端获取首页标题(无需客户端参与)

&#x20; const res = await fetch('https://api.your-blog.com/home/title');

&#x20; const { title } = await res.json();

&#x20; return (

&#x20;   \<div className="home-page">

&#x20;     \
<h1>{title}\</h1>

&#x20;     \<PostList /> {/\* 服务器组件:渲染文章列表 \*/}

&#x20;     \<Counter />  {/\* 客户端组件:提供交互能力 \*/}

&#x20;   \</div>

&#x20; );

}

export default HomePage;

二、RSC 实战核心:数据获取与渲染优化

RSC 的核心优势体现在 “服务端数据获取” 与 “流式渲染”,这也是实战中最常用的能力。以下结合 Next.js 13+ App Router(对 RSC 支持最成熟的框架)展开。

1. 数据获取优化:服务端直接请求,减少客户端等待

传统 React 应用中,客户端需 “加载 JS → 发起请求 → 渲染页面”,而 RSC 可在服务端完成 “请求数据 → 渲染组件”,客户端直接接收渲染结果,减少 1 次网络往返。

实战场景:文章详情页数据获取

需求:从数据库获取文章详情,渲染到页面,无需客户端 JS 参与。

// app/posts/\[id]/page.tsx(Next.js 动态路由,默认服务器组件)

import { notFound } from 'next/navigation'; // Next.js 导航工具

import PostContent from './PostContent.server';

import AuthorInfo from './AuthorInfo.server';

// 动态路由参数通过函数参数接收,支持 async

async function PostDetailPage({ params }: { params: { id: string } }) {

&#x20; try {

&#x20;   // 服务端直接查询数据库(此处用 Prisma ORM 示例)

&#x20;   const post = await prisma.post.findUnique({

&#x20;     where: { id: params.id },

&#x20;     include: { author: true }, // 关联查询作者信息

&#x20;   });

&#x20;   if (!post) {

&#x20;     notFound(); // 无数据时返回 404

&#x20;   }

&#x20;   return (

&#x20;     \<div className="post-detail">

&#x20;       \
<h1>{post.title}\</h1>

&#x20;       \<AuthorInfo author={post.author} /> {/\* 服务器组件:渲染作者信息 \*/}

&#x20;       \<PostContent content={post.content} /> {/\* 服务器组件:渲染文章内容 \*/}

&#x20;     \</div>

&#x20;   );

&#x20; } catch (error) {

&#x20;   // 服务端错误处理(不暴露给客户端)

&#x20;   console.error('获取文章失败:', error);

&#x20;   return \
<div>加载文章失败,请稍后重试\</div>;

&#x20; }

}

export default PostDetailPage;

关键优化点

  • 数据请求在服务端完成,客户端无需加载数据库 SDK(如 Prisma),减少 JS 包体积;

  • 动态路由参数直接传递,无需客户端解析 URL;

  • 错误处理在服务端完成,避免敏感错误信息暴露给客户端。

2. 流式渲染:用 Suspense 实现 “优先渲染核心内容”

RSC 支持流式渲染(Streaming Rendering)—— 服务端可分批次向客户端发送渲染结果,核心内容(如标题、导航)先展示,非核心内容(如评论、推荐文章)后续加载,提升用户感知速度。

实现流式渲染需配合 Suspense 组件,指定 “加载中占位符”(Fallback)。

实战场景:博客首页流式渲染

需求:先渲染首页标题和最新文章列表,评论区后续加载。

// app/page.tsx(服务器组件)

import { Suspense } from 'react'; // React 内置 Suspense,支持服务器组件

import PostList from './components/PostList.server';

import CommentSection from './components/CommentSection.server';

import LoadingSkeleton from './components/LoadingSkeleton.client'; // 客户端加载占位符

async function HomePage() {

&#x20; // 服务端获取首页标题(轻量数据,快速返回)

&#x20; const res = await fetch('https://api.your-blog.com/home/title');

&#x20; const { title } = await res.json();

&#x20; return (

&#x20;   \<div className="home-page">

&#x20;     \
<h1>{title}\</h1>

&#x20;     {/\* 核心内容:文章列表,优先渲染 \*/}

&#x20;     \<PostList />

&#x20;     {/\* 非核心内容:评论区,用 Suspense 包裹,流式加载 \*/}

&#x20;     \<Suspense fallback={\<LoadingSkeleton type="comments" />}>

&#x20;       \<CommentSection /> {/\* 异步加载的服务器组件 \*/}

&#x20;     \</Suspense>

&#x20;   \</div>

&#x20; );

}

export default HomePage;

关键逻辑

  • PostList 是轻量组件,数据请求快,优先渲染;

  • CommentSection 可能需要查询大量评论数据,请求慢,用 Suspense 包裹;

  • 服务端先发送 HomePagePostList 的渲染结果,客户端展示;

  • 评论区数据请求完成后,服务端再发送 CommentSection 的渲染结果,客户端替换 LoadingSkeleton

三、RSC 实战场景:表单、缓存与状态管理

除了数据获取,RSC 在表单处理、缓存策略、状态管理等场景也有独特的实现方式,以下是高频场景的实战代码。

1. 表单处理:Server Actions 替代客户端请求

RSC 配合 Server Actions(服务端动作),可实现 “客户端表单提交 → 服务端处理” 的闭环,无需客户端写 fetchaxios 请求,简化逻辑。

Server Actions 需用 'use server' 声明,可直接在表单 action 属性中引用。

实战场景:发布文章表单

// 1. 定义 Server Action:处理表单提交(服务端)

// app/actions/postActions.ts

'use server'; // 声明为服务端动作

import { revalidatePath } from 'next/cache'; // Next.js 缓存刷新工具

import prisma from '@/lib/prisma'; // 数据库客户端

// 表单提交逻辑(参数为 FormData,服务端直接解析)

export async function submitPost(formData: FormData) {

&#x20; // 1. 解析表单数据

&#x20; const title = formData.get('title') as string;

&#x20; const content = formData.get('content') as string;

&#x20; const authorId = formData.get('authorId') as string;

&#x20; // 2. 服务端验证(避免客户端篡改数据)

&#x20; if (!title || title.length < 5) {

&#x20;   throw new Error('文章标题至少 5 个字符');

&#x20; }

&#x20; // 3. 写入数据库

&#x20; await prisma.post.create({

&#x20;   data: { title, content, authorId },

&#x20; });

&#x20; // 4. 刷新缓存:让首页文章列表更新(Next.js 特性)

&#x20; revalidatePath('/');

&#x20; // 5. 返回结果(可选)

&#x20; return { success: true, message: '文章发布成功' };

}

// 2. 表单组件:客户端或服务器组件均可(此处用服务器组件)

// app/components/PostForm.server.tsx

import { useFormStatus } from 'react-dom'; // 用于获取表单提交状态(客户端 Hooks,需配合 'use client'?不,Next.js 14+ 支持服务器组件使用)

// 表单组件(服务器组件,无需 'use client')

export default function PostForm() {

&#x20; // 获取表单提交状态:loading、error(Next.js 提供的能力)

&#x20; const { pending, error } = useFormStatus();

&#x20; return (

&#x20;   \<form action={submitPost} className="post-form">

&#x20;     {/\* 隐藏的作者 ID(服务端可验证,避免客户端篡改) \*/}

&#x20;     \<input type="hidden" name="authorId" value="user\_123" />

&#x20;     \
<div>

&#x20;       \<label>文章标题\</label>

&#x20;       \<input&#x20;

&#x20;         type="text"&#x20;

&#x20;         name="title"&#x20;

&#x20;         required&#x20;

&#x20;         disabled={pending} // 提交中禁用输入

&#x20;       />

&#x20;     \</div>

&#x20;     \
<div>

&#x20;       \<label>文章内容\</label>

&#x20;       \<textarea&#x20;

&#x20;         name="content"&#x20;

&#x20;         required&#x20;

&#x20;         disabled={pending}

&#x20;       \>\</textarea>

&#x20;     \</div>

&#x20;     {/\* 提交按钮:显示加载状态 \*/}

&#x20;     \<button type="submit" disabled={pending}>

&#x20;       {pending ? '发布中...' : '发布文章'}

&#x20;     \</button>

&#x20;     {/\* 错误提示 \*/}

&#x20;     {error && \<p className="error">{(error as Error).message}\</p>}

&#x20;   \</form>

&#x20; );

}

核心优势

  • 表单提交逻辑在服务端,避免客户端暴露数据库操作代码;

  • 无需客户端写 fetch 请求,简化代码;

  • useFormStatus 轻松获取提交状态,无需手动管理 loading 状态。

2. 缓存策略:控制服务端数据的缓存行为

RSC 中,服务端数据请求默认会缓存(如 fetch 默认 cache: 'force-cache'),但实际场景中需灵活控制缓存(如 “实时数据不缓存”“热门数据缓存 1 小时”)。

以下是 Next.js 中常用的缓存方案(RSC 生态中最成熟的缓存实现):

(1)基础缓存:通过 fetch 选项控制

// 1. 不缓存:每次请求都获取最新数据(如实时评论)

const res = await fetch('https://api.your-blog.com/comments', {

&#x20; cache: 'no-store', // 禁用缓存

});

// 2. 缓存并定时刷新:1 小时后重新验证(如热门文章列表)

const res = await fetch('https://api.your-blog.com/hot-posts', {

&#x20; next: { revalidate: 3600 }, // 3600 秒(1 小时)后刷新缓存

});

// 3. 基于标签缓存:手动触发刷新(如发布文章后刷新文章列表)

const res = await fetch('https://api.your-blog.com/posts', {

&#x20; next: { tags: \['posts'] }, // 给缓存打标签

});

// 手动刷新标签对应的缓存(在 Server Action 中)

revalidateTag('posts');

(2)复杂缓存:用 unstable_cache 封装

对于数据库查询等非 fetch 请求,可使用 unstable_cache(Next.js 提供)封装,实现更灵活的缓存逻辑。

// app/lib/data.ts

import { unstable\_cache } from 'next/cache';

import prisma from './prisma';

// 封装缓存的“获取热门文章”函数

export const getHotPosts = unstable\_cache(

&#x20; async (limit: number) => {

&#x20;   // 复杂查询:获取点赞数前 N 的文章

&#x20;   const posts = await prisma.post.findMany({

&#x20;     where: { status: 'published' },

&#x20;     orderBy: { likes: 'desc' },

&#x20;     take: limit,

&#x20;     include: { author: { select: { name: true, avatar: true } } },

&#x20;   });

&#x20;   return posts;

&#x20; },

&#x20; \['hot-posts'], // 缓存键(变更时刷新缓存)

&#x20; {

&#x20;   revalidate: 1800, // 30 分钟自动刷新

&#x20;   tags: \['posts'], // 关联标签,可手动刷新

&#x20; }

);

// 在服务器组件中使用

// app/components/HotPosts.server.tsx

import { getHotPosts } from '@/lib/data';

async function HotPosts() {

&#x20; const posts = await getHotPosts(5); // 从缓存获取,未命中则查询数据库

&#x20; return (

&#x20;   \<div className="hot-posts">

&#x20;     \
<h3>热门文章\</h3>

&#x20;     \
<ul>

&#x20;       {posts.map(post => (

&#x20;         \<li key={post.id}>{post.title}\</li>

&#x20;       ))}

&#x20;     \</ul>

&#x20;   \</div>

&#x20; );

}

3. 状态管理:客户端状态与服务端数据的配合

RSC 中,服务端组件无状态,客户端组件负责状态管理(如 useState/useReducer)。若需 “客户端状态影响服务端数据”(如 “筛选文章分类”),可通过以下两种方式实现:

(1)客户端状态 → URL 参数 → 服务端获取

客户端状态(如筛选条件)同步到 URL 参数,服务端通过参数查询对应数据(适合需要 SEO 的场景)。

// 1. 客户端组件:筛选器(控制 URL 参数)

// app/components/CategoryFilter.client.tsx

'use client';

import { useSearchParams, usePathname, useRouter } from 'next/navigation';

export default function CategoryFilter() {

&#x20; const searchParams = useSearchParams();

&#x20; const pathname = usePathname();

&#x20; const router = useRouter();

&#x20; // 获取当前筛选的分类

&#x20; const currentCategory = searchParams.get('category') || 'all';

&#x20; // 切换分类时,更新 URL 参数

&#x20; const handleCategoryChange = (e: React.ChangeEvent\
<HTMLSelectElement>) => {

&#x20;   const category = e.target.value;
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇