Next.js 面试题详细答案 - Q6
Q6: Server Components 的优势和限制是什么?(无状态、无副作用、直接访问后端资源、更小的客户端包 vs 无法使用 state 和 effects)
Server Components 优势
1. 直接访问后端资源
// ✅ 直接连接数据库
import { db } from './lib/database'
async function UserList() {
const users = await db.user.findMany({
select: { id: true, name: true, email: true },
})
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
)
}
// ✅ 直接访问文件系统
import fs from 'fs'
import path from 'path'
async function BlogPost({ slug }) {
const filePath = path.join(process.cwd(), 'content', `${slug}.md`)
const content = fs.readFileSync(filePath, 'utf8')
return (
<article>
<div dangerouslySetInnerHTML={{ __html: content }} />
</article>
)
}
// ✅ 直接调用外部 API
async function WeatherWidget() {
const response = await fetch('https://api.weather.com/current', {
headers: {
Authorization: `Bearer ${process.env.WEATHER_API_KEY}`,
},
})
const weather = await response.json()
return (
<div>
<h3>当前天气</h3>
<p>温度: {weather.temperature}°C</p>
<p>湿度: {weather.humidity}%</p>
</div>
)
}
2. 更小的客户端包大小
// Server Component - 不发送到客户端
import { heavyLibrary } from 'heavy-library'
import { anotherHeavyDep } from 'another-heavy-dep'
async function DataProcessor() {
// 这些库只在服务器运行,不会增加客户端包大小
const processedData = heavyLibrary.process(await fetchData())
const result = anotherHeavyDep.analyze(processedData)
return <div>{result.summary}</div>
}
// 对比 Client Component
;('use client')
import { heavyLibrary } from 'heavy-library' // 会增加客户端包大小
import { useState } from 'react'
function ClientDataProcessor() {
const [data, setData] = useState(null)
useEffect(() => {
// 需要在客户端处理,增加包大小
const processed = heavyLibrary.process(data)
setData(processed)
}, [])
return <div>{data}</div>
}
3. 无状态和无副作用
// ✅ 纯函数,易于测试和预测
async function ProductCard({ productId }) {
const product = await db.product.findUnique({
where: { id: productId },
})
// 没有状态变化,输出完全由输入决定
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>价格: ${product.price}</p>
<p>库存: {product.stock}</p>
</div>
)
}
// ✅ 没有副作用,不会影响全局状态
async function UserProfile({ userId }) {
const user = await db.user.findUnique({
where: { id: userId },
})
// 只是渲染数据,不修改任何状态
return (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
)
}
4. 更好的 SEO 和首屏性能
// ✅ 服务器渲染,搜索引擎可以直接抓取
async function BlogPost({ slug }) {
const post = await fetch(`/api/posts/${slug}`)
return (
<article>
<h1>{post.title}</h1>
<meta name="description" content={post.excerpt} />
<div>{post.content}</div>
</article>
)
}
// ✅ 首屏内容立即可见
async function HomePage() {
const featuredPosts = await db.post.findMany({
where: { featured: true },
take: 3,
})
return (
<div>
<h1>精选文章</h1>
{featuredPosts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
5. 自动缓存和优化
// ✅ 自动缓存,相同请求不会重复执行
async function ExpensiveCalculation() {
// 这个计算会被缓存,相同参数不会重复计算
const result = await performExpensiveCalculation()
return <div>结果: {result}</div>
}
// ✅ 请求去重
async function UserData({ userId }) {
// 多个组件同时请求相同用户数据时,只会发送一个请求
const user = await fetch(`/api/users/${userId}`)
return <div>{user.name}</div>
}
Server Components 限制
1. 无法使用状态管理
// ❌ 不能使用 useState
async function ServerComponent() {
// const [count, setCount] = useState(0) // 错误!
return <div>静态内容</div>
}
// ❌ 不能使用 useReducer
async function ServerComponent() {
// const [state, dispatch] = useReducer(reducer, initialState) // 错误!
return <div>静态内容</div>
}
// ❌ 不能使用自定义状态 Hook
async function ServerComponent() {
// const { data, loading } = useCustomHook() // 错误!
return <div>静态内容</div>
}
2. 无法使用生命周期和副作用
// ❌ 不能使用 useEffect
async function ServerComponent() {
// useEffect(() => {
// console.log('组件挂载')
// }, []) // 错误!
return <div>静态内容</div>
}
// ❌ 不能使用 useLayoutEffect
async function ServerComponent() {
// useLayoutEffect(() => {
// // 副作用逻辑
// }, []) // 错误!
return <div>静态内容</div>
}
// ❌ 不能使用其他副作用 Hook
async function ServerComponent() {
// const ref = useRef(null) // 错误!
// const memoizedValue = useMemo(() => expensiveCalculation(), []) // 错误!
return <div>静态内容</div>
}
3. 无法处理用户交互
// ❌ 不能处理点击事件
async function ServerComponent() {
return <button onClick={() => alert('clicked')}> // 错误! 点击我</button>
}
// ❌ 不能处理表单提交
async function ServerComponent() {
return (
<form
onSubmit={(e) => {
e.preventDefault()
// 处理表单
}}
>
{' '}
// 错误!
<input type="text" />
<button type="submit">提交</button>
</form>
)
}
// ❌ 不能处理输入变化
async function ServerComponent() {
return (
<input
onChange={(e) => {
console.log(e.target.value)
}}
/> // 错误!
)
}
4. 无法访问浏览器 API
// ❌ 不能访问 window 对象
async function ServerComponent() {
// const userAgent = window.navigator.userAgent // 错误!
return <div>静态内容</div>
}
// ❌ 不能访问 localStorage
async function ServerComponent() {
// const data = localStorage.getItem('key') // 错误!
return <div>静态内容</div>
}
// ❌ 不能访问 document
async function ServerComponent() {
// const title = document.title // 错误!
return <div>静态内容</div>
}
// ❌ 不能使用浏览器特定的 API
async function ServerComponent() {
// navigator.geolocation.getCurrentPosition(...) // 错误!
// window.addEventListener('resize', ...) // 错误!
return <div>静态内容</div>
}
5. 无法使用 Context
// ❌ 不能使用 useContext
async function ServerComponent() {
// const theme = useContext(ThemeContext) // 错误!
return <div>静态内容</div>
}
// ❌ 不能使用 Context Provider
async function ServerComponent() {
return (
<ThemeProvider value="dark">
{' '}
// 错误!
<ChildComponent />
</ThemeProvider>
)
}
实际应用中的权衡
1. 混合使用策略
// Server Component - 数据获取和静态内容
async function BlogPost({ slug }) {
const post = await fetch(`/api/posts/${slug}`)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* 嵌入 Client Component 处理交互 */}
<LikeButton postId={post.id} />
<CommentSection postId={post.id} />
</article>
)
}
// Client Component - 处理交互
;('use client')
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false)
const handleLike = async () => {
setLiked(!liked)
await fetch(`/api/posts/${postId}/like`, {
method: 'POST',
})
}
return <button onClick={handleLike}>{liked ? '❤️' : '🤍'}</button>
}
2. 数据传递模式
// Server Component 获取数据,传递给 Client Component
async function ProductPage({ params }) {
const product = await db.product.findUnique({
where: { id: params.id },
})
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* 传递服务器数据给客户端组件 */}
<ProductActions product={product} />
</div>
)
}
;('use client')
function ProductActions({ product }) {
const [quantity, setQuantity] = useState(1)
const addToCart = () => {
// 使用服务器传递的数据
console.log(`添加 ${quantity} 个 ${product.name} 到购物车`)
}
return (
<div>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(Number(e.target.value))}
/>
<button onClick={addToCart}>加入购物车</button>
</div>
)
}
最佳实践
1. 组件选择原则
// ✅ 优先使用 Server Components
// - 数据获取
// - 静态内容
// - SEO 重要内容
// ✅ 必要时使用 Client Components
// - 用户交互
// - 状态管理
// - 浏览器 API
2. 性能优化
// 将 Client Components 放在组件树的叶子节点
async function BlogPost({ slug }) {
const post = await fetch(`/api/posts/${slug}`)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* 只在需要交互的地方使用 Client Component */}
<InteractiveElements postId={post.id} />
</article>
)
}
总结
Server Components 的优势:
- 直接访问后端资源:数据库、文件系统、外部 API
- 更小的客户端包:减少 JavaScript 传输
- 无状态无副作用:易于测试和预测
- 更好的 SEO:服务器渲染内容
- 自动优化:缓存和请求去重
Server Components 的限制:
- 无状态管理:不能使用 useState、useReducer
- 无生命周期:不能使用 useEffect、useLayoutEffect
- 无用户交互:不能处理事件
- 无浏览器 API:不能访问 window、document
- 无 Context:不能使用 useContext
通过合理使用两种组件类型,可以构建既高性能又交互丰富的应用。