Next.js 中的 React 服务器与客户端组件:理解差异
Source: Dev.to
请提供您希望翻译的具体文本内容,我将为您翻译成简体中文。
介绍
React 团队在 React 18 中引入了 Server Components 作为实验性特性。它们允许 React 应用的部分在服务器上渲染,从而减少发送到客户端的 JavaScript 并实现更高效的数据获取。
Next.js 在其 App Router(在版本 13 中引入)中采用了这一概念。该框架将 React 的服务器渲染能力与自身的路由、数据获取和性能优化相结合。
Server Components
- 仅在服务器上运行——它们永远不会在浏览器中执行。
- 不能使用仅限客户端的特性,例如
useState、useEffect或window。 - 可以安全地从数据库或 API 获取数据,并仅返回生成的 HTML。
- 从客户端包中排除 JavaScript,从而减小包体积并提升性能。
- 可以导入 Client Components,但 Client Components 不能导入 Server Components。
// This is a Server Component (default)
export default async function ServerComponent() {
const posts = await fetchPosts();
return (
<ul>
{posts.map(post => (
<li key={post.id}>- {post.title}</li>
))}
</ul>
);
}
默认情况下,Next.js App Router(
app/目录)中的所有组件都是 Server Components,除非另有标记。
客户端组件
客户端组件是传统的 React 组件,在浏览器中运行。当你需要交互、状态或副作用时必须使用它们。
要将文件标记为客户端组件,请在文件顶部添加 "use client" 指令:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
关键要点
- 在服务器上渲染初始 HTML(预渲染),随后在浏览器中 hydrate(水化)。
- 可以使用 React 状态 Hook(
useState、useEffect等)以及浏览器 API。 - 不能直接导入服务器组件,但可以通过 props 接收它们。
组合服务器组件和客户端组件
将服务器组件传递给客户端组件
服务器组件可以渲染另一个服务器组件,并将其作为属性传递给客户端组件。服务器渲染该节点、序列化它,客户端随后将其作为 React 元素接收。
// Server Component
export function ServerInfo() {
return <div>Server time: {new Date().toISOString()}</div>;
}
// Client Component (Wrapper)
"use client";
export function Wrapper({ info }) {
return (
<section>
## Wrapped content:
{info}
</section>
);
}
// Server Page
import { ServerInfo } from "./ServerInfo";
import { Wrapper } from "./Wrapper";
export default function Page() {
return <Wrapper info={<ServerInfo />} />;
}
在服务器组件内部使用客户端组件
// Server Component (page)
import Counter from "./Counter"; // ✅ allowed (Client Component)
export default async function Page() {
const user = await fetchUser();
return (
<section>
<h1>Hello, {user.name}</h1>
<Counter />
</section>
);
}
Hydration
Hydration 是 React 将交互性附加到服务器生成的静态 HTML 的过程。常见的陷阱包括:
- 非确定性渲染(例如
Math.random()、Date.now()),会导致服务器端和客户端生成不同的标记。 - 服务器端和客户端之间的条件渲染差异。
- 当服务器和客户端数据不一致时,属性(props)不匹配。
Best practices
- 在同时在服务器端和客户端渲染的组件中使用确定性数据。
- 将交互性隔离在小且定义明确的客户端组件中。
- 将动态逻辑放入
useEffect或其他仅客户端作用域中。
Comparison
| 功能 | 服务器组件 | 客户端组件 |
|---|---|---|
| 执行 | 仅服务器 | 服务器预渲染 + 浏览器水合 |
是否可以使用 React 状态钩子 (useState, useEffect) | ❌ | ✅ |
| 安全的数据获取 | ✅(直接) | ⚠️(通过 API 路由) |
| 导入方向 | ✅ 可导入客户端组件 | ❌ 不能导入服务器组件 |
| 是否可以接收服务器组件作为属性 | ✅ | ✅ |
| 对包大小的影响 | 最小 | 增加包大小 |
| SEO | 优秀 | 也很好(已预渲染) |
| 典型用例 | 静态内容,数据密集型渲染 | 交互式 UI、表单、动画 |
摘要
- Server Components 适用于数据密集或静态内容;它们仅在服务器上运行,保持客户端包体积小,并提升 SEO。
- Client Components 提供交互性;它们在服务器上预渲染,然后在浏览器中进行水化。
- 导入流程遵循 Server → Client(允许),但 Client → Server 被禁止。
- 将 Server Components 作为属性传递给 Client Components 可以实现强大的组合,同时保留两种渲染模型的优势。
如果正确使用,这种架构会让你的 Next.js 应用更快、更安全、更易维护——这对现代 React 开发来说是一次真正的胜利。