Appearance
第6章:Server Components 与 Client Components(Next.js 14 核心)
6.1 什么是 Server Components(服务端组件)与 Client Components(客户端组件)
Server Components(服务端组件)
Server Components 是 Next.js 13+ 引入的一种新组件类型,它们在服务器端渲染,不需要发送到客户端。
核心特点:
- 在服务器端渲染,不包含任何客户端 JavaScript
- 可以直接访问后端资源(数据库、API 等)
- 减少客户端 bundle 大小,提升加载性能
- 支持异步数据获取,无需 useEffect
示例:
jsx
// app/components/ServerComponent.jsx
// 服务端组件,默认不需要 'use client' 指令
export default async function ServerComponent() {
// 直接在服务端获取数据
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return (
<div>
<h1>Server Component</h1>
<p>Data from API: {data.message}</p>
</div>
);
}Client Components(客户端组件)
Client Components 是传统的 React 组件,在客户端渲染,支持交互和状态管理。
核心特点:
- 在客户端渲染,包含客户端 JavaScript
- 支持交互(点击、输入等事件)
- 支持状态管理(useState、useReducer 等)
- 支持浏览器 API(window、document 等)
示例:
jsx
// app/components/ClientComponent.jsx
// 客户端组件,需要 'use client' 指令
'use client';
import { useState } from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Client Component</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}6.2 两者核心区别(渲染位置、是否支持交互、API 访问权限)
| 特性 | Server Components | Client Components |
|---|---|---|
| 渲染位置 | 服务器端 | 客户端 |
| 客户端 JavaScript | 无 | 有 |
| 支持交互 | 否(无事件处理) | 是 |
| 状态管理 | 否(无 useState 等) | 是 |
| 浏览器 API | 否(无 window、document 等) | 是 |
| 数据获取 | 直接使用 async/await | 需要 useEffect 或 SWR/React Query |
| 后端资源访问 | 直接访问(数据库、API 等) | 间接访问(通过 API) |
| 性能影响 | 减少客户端 bundle 大小 | 增加客户端 bundle 大小 |
6.3 使用规则('use client' 指令使用,新手避坑)
'use client' 指令
- 作用:标记组件为客户端组件
- 位置:必须放在文件的第一行,在任何 import 语句之前
- 传播:如果一个组件被标记为客户端组件,它的所有子组件也会成为客户端组件
正确使用:
jsx
// 正确:'use client' 放在第一行
'use client';
import { useState } from 'react';
export default function ClientComponent() {
// ...
}错误使用:
jsx
// 错误:'use client' 不在第一行
import { useState } from 'react';
'use client'; // 这会导致错误
export default function ClientComponent() {
// ...
}新手避坑
不要在服务端组件中使用客户端特性:
- 不要使用 useState、useEffect 等 hooks
- 不要使用 window、document 等浏览器 API
- 不要添加事件监听器(onClick、onChange 等)
不要在客户端组件中直接访问后端资源:
- 不要直接访问数据库
- 不要使用服务器端特定的 API
合理划分组件:
- 将无交互、数据展示的部分放在服务端组件
- 将需要交互、状态管理的部分放在客户端组件
注意组件传播:
- 一旦标记为客户端组件,所有子组件都会成为客户端组件
- 尽量将客户端逻辑隔离在最小的组件中
6.4 适用场景(什么时候用服务端组件,什么时候用客户端组件)
服务端组件适用场景
数据展示:
- 博客文章、产品列表、用户资料等
- 不需要用户交互的静态内容
直接数据获取:
- 需要从数据库或 API 获取数据的组件
- 可以直接在组件中使用 async/await 获取数据
减少客户端 bundle:
- 大型组件库、复杂逻辑的组件
- 不需要交互的 heavy 组件
访问后端资源:
- 需要直接访问数据库、文件系统等后端资源的组件
示例:
jsx
// app/blog/[id]/page.js
// 服务端组件,用于获取和展示博客文章
export default async function BlogPost({ params }) {
const { id } = params;
// 直接在服务端获取数据
const response = await fetch(`https://api.example.com/posts/${id}`);
const post = await response.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
<p>Author: {post.author}</p>
<p>Date: {post.date}</p>
</div>
);
}客户端组件适用场景
用户交互:
- 表单、按钮、下拉菜单等
- 需要用户输入或点击的组件
状态管理:
- 需要使用 useState、useReducer 等 hooks 的组件
- 需要保持状态的组件
浏览器 API:
- 需要使用 window、document、localStorage 等浏览器 API 的组件
- 需要访问浏览器特性的组件
实时更新:
- 需要实时更新的组件(如计数器、实时聊天等)
- 需要 WebSocket 连接的组件
示例:
jsx
// app/components/CommentForm.jsx
// 客户端组件,用于提交评论
'use client';
import { useState } from 'react';
export default function CommentForm({ postId }) {
const [comment, setComment] = useState('');
const [submitting, setSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
try {
const response = await fetch(`/api/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ postId, comment }),
});
if (response.ok) {
setComment('');
alert('Comment submitted successfully!');
} else {
alert('Failed to submit comment');
}
} catch (error) {
alert('Error submitting comment');
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Write a comment..."
rows={4}
/>
<button type="submit" disabled={submitting}>
{submitting ? 'Submitting...' : 'Submit Comment'}
</button>
</form>
);
}6.5 组件通信(服务端组件与客户端组件数据传递)
服务端组件向客户端组件传递数据
服务端组件可以通过 props 向客户端组件传递数据。
示例:
jsx
// app/page.js
// 服务端组件
export default async function Home() {
// 在服务端获取数据
const response = await fetch('https://api.example.com/products');
const products = await response.json();
return (
<div>
<h1>Products</h1>
{/* 向客户端组件传递数据 */}
<ProductList products={products} />
</div>
);
}
// app/components/ProductList.jsx
// 客户端组件
'use client';
import { useState } from 'react';
export default function ProductList({ products }) {
const [selectedCategory, setSelectedCategory] = useState('all');
const filteredProducts = selectedCategory === 'all'
? products
: products.filter(product => product.category === selectedCategory);
const categories = [...new Set(products.map(product => product.category))];
return (
<div>
<div>
<button
onClick={() => setSelectedCategory('all')}
className={selectedCategory === 'all' ? 'active' : ''}
>
All
</button>
{categories.map(category => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className={selectedCategory === category ? 'active' : ''}
>
{category}
</button>
))}
</div>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>
<h3>{product.name}</h3>
<p>{product.price}</p>
</li>
))}
</ul>
</div>
);
}客户端组件向服务端组件传递数据
客户端组件无法直接向服务端组件传递数据,但可以通过 URL 参数、表单提交或 API 请求来实现。
示例:
jsx
// app/search/page.js
// 服务端组件
export default async function SearchPage({ searchParams }) {
const query = searchParams.q || '';
// 根据查询参数获取数据
const response = await fetch(`https://api.example.com/search?q=${encodeURIComponent(query)}`);
const results = await response.json();
return (
<div>
<h1>Search Results for "{query}"</h1>
<SearchForm initialQuery={query} />
<ul>
{results.map(result => (
<li key={result.id}>
<h3>{result.title}</h3>
<p>{result.description}</p>
</li>
))}
</ul>
</div>
);
}
// app/components/SearchForm.jsx
// 客户端组件
'use client';
import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
export default function SearchForm({ initialQuery }) {
const [query, setQuery] = useState(initialQuery);
const router = useRouter();
const handleSubmit = (e) => {
e.preventDefault();
// 通过 URL 参数传递数据给服务端组件
router.push(`/search?q=${encodeURIComponent(query)}`);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<button type="submit">Search</button>
</form>
);
}小结
本章介绍了 Next.js 14 的核心特性:Server Components 与 Client Components。通过本章的学习,你应该已经掌握了:
- Server Components 和 Client Components 的概念和特点
- 两者的核心区别:渲染位置、是否支持交互、API 访问权限等
- 使用规则:'use client' 指令的正确使用和新手避坑
- 适用场景:什么时候使用服务端组件,什么时候使用客户端组件
- 组件通信:服务端组件与客户端组件之间的数据传递
Server Components 是 Next.js 14 的重要创新,它通过在服务器端渲染组件,减少了客户端 bundle 大小,提升了应用性能。合理使用 Server Components 和 Client Components,可以构建出性能更好、用户体验更佳的 Next.js 应用。
在实际开发中,建议:
- 将无交互、数据展示的组件设计为服务端组件
- 将需要交互、状态管理的组件设计为客户端组件
- 合理划分组件职责,充分利用 Server Components 的性能优势
接下来,我们将学习 Next.js 的数据获取,这是 Next.js 开发中的核心难点之一。
