属于我们的Performance & Scalability系列
阅读完整指南React 19 服务器组件:发生了什么变化以及原因
React 19 是自 hooks 以来最重要的版本。服务器组件最初是 React 18 中的一个实验性功能,现在已经稳定并与并发渲染模型完全集成。但这些变化远远超出了稳定 RSC 的范围——React 19 引入了 Actions、一个新的 use() 钩子、表单集成、乐观更新和文档元数据管理,它们共同改变了您对 React 应用程序中数据流的看法。
本指南重点关注 React 18 和 19 之间实际发生的变化,解释每个变化背后的架构推理,并展示替代旧方法的生产模式。
要点
- 服务器组件仅在服务器上运行 - 它们没有生命周期,没有状态,也没有浏览器 API
use()钩子替换了客户端组件中的await以使用承诺和上下文- React 19 Actions 替换了表单提交的手动
useState+fetch模式useOptimistic在服务器确认之前启用即时 UI 更新useFormStatus为您提供挂起状态,无需通过表单组件进行道具钻取- 资源预加载 API(
preload、preinit)可让您控制组件的资源加载- 组件中的
<title>、<meta>和<link>标签自动提升到<head>- 服务器组件和客户端组件形成树 - 客户端组件无法导入服务器组件
服务器组件实际上是什么
在讨论更改内容之前,先澄清什么是服务器组件:专门在服务器上呈现并生成客户端水合的 HTML + RSC 有效负载的 React 组件。它们与服务器端呈现的客户端组件不同。
主要区别:
| 服务器组件 | 客户端组件 | |
|---|---|---|
| 运行于 | 仅限服务器 | 服务器(初始渲染)+客户端 |
| 可以使用钩子 | 没有 | 是的 |
| 可以使用浏览器API | 没有 | 是的 |
| 可以访问数据库 | 是(直接) | 否(通过 API) |
| 捆绑尺寸影响 | 零 | 是的 |
| 可以是异步的 | 是的 | 否(毫无悬念) |
// Server Component — async, no hooks, direct DB access
async function UserProfile({ userId }: { userId: string }) {
// Direct database query — no API needed
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
});
return (
<div>
<h1>{user.name}</h1>
{/* Client Component gets data as props */}
<UserActions userId={user.id} role={user.role} />
</div>
);
}
// Client Component — can use hooks, handles interactions
'use client';
function UserActions({ userId, role }: { userId: string; role: string }) {
const [isEditing, setIsEditing] = useState(false);
return (
<div>
<button onClick={() => setIsEditing(true)}>Edit</button>
{isEditing && <EditUserForm userId={userId} />}
</div>
);
}
反应 19 个动作
React 19 中最大的人体工程学改进是 Actions——一种处理由用户交互(特别是表单提交)触发的异步操作的标准化方法。
在 React 19 之前,表单处理是重复的样板:
// React 18 — manual state management
function ContactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitting(true);
setError(null);
try {
await createContact({ name, email });
} catch (err) {
setError(err.message);
} finally {
setSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
{/* ... */}
</form>
);
}
React 19 Actions 显着简化了这一过程:
// React 19 — useActionState
'use client';
import { useActionState } from 'react';
async function createContactAction(
prevState: { error?: string },
formData: FormData
) {
'use server'; // Server Action — runs on the server
const name = formData.get('name') as string;
const email = formData.get('email') as string;
try {
await createContact({ name, email });
return { success: true };
} catch (err) {
return { error: err.message };
}
}
function ContactForm() {
const [state, formAction, isPending] = useActionState(
createContactAction,
{}
);
return (
<form action={formAction}>
<input name="name" required />
<input name="email" type="email" required />
{state.error && <p className="text-red-500">{state.error}</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
useActionState 挂钩在一处管理挂起状态、表单数据和错误处理。操作函数上的 'use server' 指令将其标记为服务器操作 - 它在服务器上运行,可以直接访问数据库,并且客户端永远看不到实现。
服务器操作
服务器操作是带有 'use server' 指令的异步函数,当从客户端调用时,它们在服务器上运行。它们取代了大多数数据突变用例的 API 路由:
// app/actions/contacts.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { db } from '@ecosire/db';
import { contacts } from '@ecosire/db/schema';
export async function createContact(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
if (!name || !email) {
throw new Error('Name and email are required');
}
await db.insert(contacts).values({
name,
email,
organizationId: await getOrganizationId(), // From session
});
revalidatePath('/dashboard/contacts'); // Invalidate cached page
redirect('/dashboard/contacts'); // Redirect after success
}
服务器操作可以直接在 form 元素中使用:
import { createContact } from '@/app/actions/contacts';
export default function NewContactPage() {
return (
<form action={createContact}>
<input name="name" placeholder="Name" required />
<input name="email" placeholder="Email" type="email" required />
<button type="submit">Create Contact</button>
</form>
);
}
没有 onSubmit 处理程序,没有 preventDefault(),没有手册 fetch() — 表单就可以工作。
use() 钩子
React 19 引入了 use() 钩子,用于在渲染内消耗资源(Promises 和 Context)。与 await 不同,它适用于 React 的 Suspense 系统:
// Before React 19 — had to resolve at the top level
export default async function Page() {
const posts = await getPosts(); // Blocks entire page
return <PostList posts={posts} />;
}
// React 19 — pass a promise, resolve with use()
export default function Page() {
const postsPromise = getPosts(); // Start fetch immediately
return (
<Suspense fallback={<PostsSkeleton />}>
<PostList postsPromise={postsPromise} />
</Suspense>
);
}
// Client Component using use() to consume the promise
'use client';
function PostList({ postsPromise }: { postsPromise: Promise<Post[]> }) {
const posts = use(postsPromise); // Suspends until resolved
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
use() 钩子还取代了 useContext() ——它可以有条件地使用上下文(与钩子不同,use() 可以在条件和循环内部调用):
'use client';
import { use } from 'react';
import { ThemeContext } from '@/contexts/theme';
function Button({ children }: { children: React.ReactNode }) {
const theme = use(ThemeContext); // Can be inside conditions
return (
<button className={theme === 'dark' ? 'bg-gray-800' : 'bg-white'}>
{children}
</button>
);
}
使用 useOptimistic 进行乐观更新
useOptimistic 在服务器操作完成之前提供即时 UI 反馈,如果操作失败则自动恢复:
'use client';
import { useOptimistic, useTransition } from 'react';
interface Like {
id: string;
count: number;
userLiked: boolean;
}
function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: Like }) {
const [likes, setOptimisticLikes] = useOptimistic(
initialLikes,
(state, action: 'like' | 'unlike') => ({
...state,
count: action === 'like' ? state.count + 1 : state.count - 1,
userLiked: action === 'like',
})
);
const [isPending, startTransition] = useTransition();
async function toggleLike() {
const action = likes.userLiked ? 'unlike' : 'like';
startTransition(async () => {
setOptimisticLikes(action); // Instant UI update
// Server operation — if this fails, optimistic state reverts
await togglePostLike(postId, action);
});
}
return (
<button onClick={toggleLike} disabled={isPending}>
{likes.userLiked ? '♥' : '♡'} {likes.count}
</button>
);
}
乐观更新立即显示,然后提交(服务器成功)或恢复(服务器失败)。用户无需等待网络往返即可获得即时反馈。
组件的文档元数据
React 19 允许在任何组件内呈现 <title>、<meta> 和 <link> 标签 - 它们会自动提升到文档 <head>:
// Server Component — metadata hoists to <head> automatically
async function BlogPost({ slug }: { slug: string }) {
const post = await getPost(slug);
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.description} />
<link rel="canonical" href={`https://ecosire.com/blog/${slug}`} />
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
这适用于服务器和客户端组件。在 Next.js 等框架中,您仍将使用 generateMetadata() 来完全控制 OpenGraph 标签和 hreflang 属性 - React 19 的本机支持更为基本。但对于简单的情况,它消除了对 next/head 或类似库的需要。
资源加载API
React 19 提供了用于预加载资源的显式 API,让您可以对资源加载瀑布进行细粒度控制:
import { preload, preinit, prefetchDNS, preconnect } from 'react-dom';
function BelowFoldSection() {
// When this component renders, preload the hero image for next section
preload('/images/hero-next.webp', { as: 'image' });
// Preinit a script (loads and executes immediately)
preinit('https://cdn.example.com/analytics.js', { as: 'script' });
// DNS prefetch for external resources
prefetchDNS('https://fonts.googleapis.com');
// Establish connection early
preconnect('https://api.ecosire.com');
return <section>{/* content */}</section>;
}
这些 API 可以与 React 的渲染模型一起正常工作——它们批处理资源提示并在文档中的最佳点发出它们,甚至从组件树的深处发出它们。
常见陷阱和解决方案
陷阱 1:尝试在服务器组件中使用钩子
服务器组件不能使用 useState、useEffect、useContext 或任何其他挂钩。如果您需要这些,请将 'use client' 添加到组件中。错误通常是:Error: Hooks can only be called inside a function component。
陷阱 2:从客户端组件导入服务器组件
客户端组件无法导入服务器组件(反之亦然)。这是因为客户端组件在不存在服务器组件的浏览器中运行。如果您需要组合它们,请将服务器组件作为 children 属性传递:
// Wrong — Client Component cannot import Server Component
'use client';
import { ServerUserProfile } from './server-user-profile'; // Error
// Correct — pass as children from a Server Component parent
// Parent (Server):
<ClientShell>
<ServerUserProfile userId={userId} />
</ClientShell>
// ClientShell:
'use client';
function ClientShell({ children }: { children: React.ReactNode }) {
return <div className="shell">{children}</div>;
}
陷阱 3:将 props 从服务器传递到客户端时出现序列化错误
只有 JSON 可序列化数据才能作为 props 跨越服务器-客户端边界。函数、类实例、Map、Set 和 Date(作为对象)无法序列化。将日期转换为 ISO 字符串;用字符串标识符替换函数。
陷阱 4:服务器操作不验证输入
永远不要相信服务器操作中客户端提供的数据。在写入数据库之前始终使用 Zod 或类似工具进行验证:
'use server';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2).max(255),
email: z.string().email(),
});
export async function createContact(formData: FormData) {
const result = schema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
});
if (!result.success) {
return { error: result.error.flatten().fieldErrors };
}
await db.insert(contacts).values(result.data);
}
常见问题
服务器组件在 Next.js 之外可用吗?
React 服务器组件是 React 的一项功能,但它们需要一个框架来处理服务器渲染基础设施——路由、捆绑、流。 Next.js App Router 是最成熟的实现。基于 Remix、Astro 和 Vite 的设置正在添加 RSC 支持。在没有框架的情况下使用 RSC 需要大量的定制基础设施工作。
服务器操作与 REST API 路由相比如何?
对于与 UI 并置的突变,服务器操作更简单 — 无需管理端点 URL,无需写入 fetch() 调用,无需错误处理样板。 REST API 路由更适合从 Web 应用程序外部(移动应用程序、Webhook、第三方集成)调用的操作、需要文档的公共 API 以及需要显式 HTTP 状态代码时。根据用例在同一应用程序中使用两者。
服务器组件对性能有什么影响?
服务器组件减少了 JavaScript 包的大小(组件代码永远不会发送到浏览器),消除客户端数据获取瀑布,并通过 Suspense 启用流式 HTML 交付。权衡是服务器计算成本——渲染发生在您的服务器上,而不是客户端的设备上。对于数据量大的页面来说,这几乎总是一个净胜。
我可以在代码库中混合使用 React 18 和 React 19 吗?
所有 React 代码都以 React 19 运行——没有每个文件的版本控制。问题是您现有的 React 18 代码是否可以在 React 19 中工作。大多数 React 18 代码都可以保持不变。主要的重大更改是围绕 refs (现在是常规属性)、ReactDOM.render 删除以及 @types/react 中的一些类型更改。运行 React 19 codemods 以进行自动迁移。
如何测试服务器组件?
服务器组件可以使用 async render 通过 React 测试库进行测试。对于服务器操作,将它们作为带有模拟数据库调用的普通异步函数进行测试。 Playwright 的端到端测试涵盖了完整的服务器组件 + 客户端组件集成,无需任何特殊设置 - 他们测试最终的 HTML 输出。
后续步骤
React 19 服务器组件代表了现代 Web 应用程序构建方式的根本转变。 ECOSIRE 的前端团队在此架构上构建了生产应用程序 — 249 个页面,其中包含服务器组件、服务器操作和为实际用户工作流程提供支持的乐观更新。
如果您需要从 React 18 迁移到 19 的帮助、构建新的 RSC 优先应用程序,或者只是需要团队中的专家前端工程,请探索我们的开发服务。
作者
ECOSIRE Research and Development Team
在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。
相关文章
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Next.js 16 App Router: Production Patterns and Pitfalls
Production-ready Next.js 16 App Router patterns: server components, caching strategies, metadata API, error boundaries, and performance pitfalls to avoid.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
更多来自Performance & Scalability
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Odoo vs Acumatica: Cloud ERP for Growing Businesses
Odoo vs Acumatica compared for 2026: unique pricing models, scalability, manufacturing depth, and which cloud ERP fits your growth trajectory.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.
Compliance Monitoring Agents with OpenClaw
Deploy OpenClaw AI agents for continuous compliance monitoring. Automate regulatory checks, policy enforcement, audit trail generation, and compliance reporting.