Q6: Server Components 的优势和限制是什么?(无状态、无副作用、直接访问后端资源、更小的客户端包 vs 无法使用 state 和 effec

31 阅读2分钟

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

通过合理使用两种组件类型,可以构建既高性能又交互丰富的应用。