Skip to content

第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 ComponentsClient 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() {
  // ...
}

新手避坑

  1. 不要在服务端组件中使用客户端特性

    • 不要使用 useState、useEffect 等 hooks
    • 不要使用 window、document 等浏览器 API
    • 不要添加事件监听器(onClick、onChange 等)
  2. 不要在客户端组件中直接访问后端资源

    • 不要直接访问数据库
    • 不要使用服务器端特定的 API
  3. 合理划分组件

    • 将无交互、数据展示的部分放在服务端组件
    • 将需要交互、状态管理的部分放在客户端组件
  4. 注意组件传播

    • 一旦标记为客户端组件,所有子组件都会成为客户端组件
    • 尽量将客户端逻辑隔离在最小的组件中

6.4 适用场景(什么时候用服务端组件,什么时候用客户端组件)

服务端组件适用场景

  1. 数据展示

    • 博客文章、产品列表、用户资料等
    • 不需要用户交互的静态内容
  2. 直接数据获取

    • 需要从数据库或 API 获取数据的组件
    • 可以直接在组件中使用 async/await 获取数据
  3. 减少客户端 bundle

    • 大型组件库、复杂逻辑的组件
    • 不需要交互的 heavy 组件
  4. 访问后端资源

    • 需要直接访问数据库、文件系统等后端资源的组件

示例

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>
  );
}

客户端组件适用场景

  1. 用户交互

    • 表单、按钮、下拉菜单等
    • 需要用户输入或点击的组件
  2. 状态管理

    • 需要使用 useState、useReducer 等 hooks 的组件
    • 需要保持状态的组件
  3. 浏览器 API

    • 需要使用 window、document、localStorage 等浏览器 API 的组件
    • 需要访问浏览器特性的组件
  4. 实时更新

    • 需要实时更新的组件(如计数器、实时聊天等)
    • 需要 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。通过本章的学习,你应该已经掌握了:

  1. Server Components 和 Client Components 的概念和特点
  2. 两者的核心区别:渲染位置、是否支持交互、API 访问权限等
  3. 使用规则:'use client' 指令的正确使用和新手避坑
  4. 适用场景:什么时候使用服务端组件,什么时候使用客户端组件
  5. 组件通信:服务端组件与客户端组件之间的数据传递

Server Components 是 Next.js 14 的重要创新,它通过在服务器端渲染组件,减少了客户端 bundle 大小,提升了应用性能。合理使用 Server Components 和 Client Components,可以构建出性能更好、用户体验更佳的 Next.js 应用。

在实际开发中,建议:

  • 将无交互、数据展示的组件设计为服务端组件
  • 将需要交互、状态管理的组件设计为客户端组件
  • 合理划分组件职责,充分利用 Server Components 的性能优势

接下来,我们将学习 Next.js 的数据获取,这是 Next.js 开发中的核心难点之一。

© 2026 编程马·菜鸟教程 版权所有