•10 min read

React Server Components vs Client Components: Phân tích chuyên sâu

React Server Components vs Client Components: Phân tích chuyên sâu

Việc phát hành React Server Components (RSC) đã thay đổi cơ bản các mô hình kiến trúc của ứng dụng React. Trước RSC, React chủ yếu tập trung vào việc hiển thị các component tương tác trong trình duyệt (hoặc đôi khi pre-render chúng thành HTML thông qua SSR). Với React Server Components, giờ đây chúng ta có một sự phân chia rõ ràng: các component chỉ chạy trên máy chủ và các component chạy trên máy khách.

Trong hướng dẫn này, chúng ta sẽ phân tích những khác biệt cốt lõi, lợi ích kiến trúc của phương pháp kết hợp này và ma trận quyết định khi nào nên sử dụng Server Component so với Client Component.

Audio Briefing
0:00 / 0:00

React Server Components là gì?

React Server Components là các component chỉ chạy trên máy chủ. Chúng không bao giờ được gửi đến trình duyệt dưới dạng JavaScript. Thay vào đó, máy chủ thực thi logic của React component, tìm nạp mọi dữ liệu cần thiết trực tiếp từ cơ sở dữ liệu hoặc API nội bộ và truyền trực tiếp HTML/UI kết quả đến máy khách.

Ưu điểm của Server Components

  1. Không có JavaScript phía máy khách: Vì Server Components không bao giờ thực thi trong trình duyệt, chúng không làm tăng kích thước gói JavaScript của ứng dụng. Bạn có thể nhập các thư viện lớn (như trình phân tích cú pháp markdown nặng hoặc thư viện định dạng ngày) bên trong Server Component mà không ảnh hưởng đến kết nối mạng của người dùng.
  2. Truy cập dữ liệu trực tiếp: Server Components thực thi trong một môi trường đáng tin cậy. Bạn có thể truy vấn cơ sở dữ liệu trực tiếp từ component của mình một cách an toàn mà không cần phải tạo một tuyến API trung gian hoặc xử lý useEffect các vấn đề về tìm nạp dữ liệu.
  3. Cải thiện thời gian tải ban đầu: Máy chủ có thể truyền UI đã render đến máy khách một cách tăng dần, cho phép người dùng xem và tương tác với trang nhanh hơn nhiều so với việc chờ tải xuống và hydrate một gói JS lớn.

Hạn chế: Không có tính tương tác

Vì Server Components không bao giờ chạy trong trình duyệt, chúng không thể sử dụng các API chỉ dành cho trình duyệt hoặc các React hook phụ thuộc vào trạng thái phía máy khách. Bạn không thể sử dụng useState, useEffect, onClick hoặc window.localStorage bên trong Server Component.

Advertisement

Client Components là gì?

Client Components là các React component tiêu chuẩn mà bạn đã quen thuộc. Chúng được gửi đến trình duyệt dưới dạng JavaScript, nơi chúng được thực thi và hydrate để cung cấp khả năng tương tác phong phú. Trong Next.js App Router, bạn đánh dấu rõ ràng một component là Client Component bằng cách đặt chỉ thị "use client" ở đầu tệp.

Khi nào nên sử dụng Client Components

Bạn phải sử dụng Client Components bất cứ khi nào UI của bạn yêu cầu tính tương tác hoặc trạng thái phía máy khách:

  • Xử lý các sự kiện người dùng như onClick, onChange hoặc gửi biểu mẫu.
  • Quản lý trạng thái cục bộ với useState hoặc logic phức tạp với useReducer.
  • Sử dụng các tác dụng phụ thông qua useEffect (ví dụ: đăng ký WebSocket hoặc tương tác với DOM).
  • Sử dụng các API trình duyệt như Geolocation hoặc IntersectionObserver.

Kiến trúc kết hợp tối ưu

Sức mạnh thực sự của React hiện đại nằm ở việc kết hợp cả hai mô hình. Thực hành tốt nhất là xây dựng toàn bộ cây ứng dụng từ Server Components, sau đó "rắc" Client Components chỉ vào các lá cụ thể của cây yêu cầu tính tương tác.

Ví dụ: Bố cục bài đăng blog

Hãy tưởng tượng xây dựng một trang bài đăng blog.

  • Bố cục, Điều hướng và Nội dung Blog hoàn toàn tĩnh và yêu cầu tìm nạp dữ liệu từ CMS hoặc cơ sở dữ liệu. Tất cả những thứ này nên là Server Components.
  • Tuy nhiên, ở cuối bài đăng, bạn có một "Nút Thích" và một "Phần Bình luận". Những thứ này yêu cầu trình xử lý onClick và trạng thái phía máy khách. Bạn sẽ trích xuất chỉ những phần này vào các tệp riêng biệt, đánh dấu chúng bằng "use client" và nhập chúng vào bố cục Server Component của bạn.
// This is a Server Component (default in Next.js)
import db from '@/lib/db';
import LikeButton from './LikeButton'; // Client Component

export default async function BlogPost({ id }) {
  // Direct, secure database access
  const post = await db.post.findUnique({ where: { id } });

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      
      {/* We pass static data to an interactive Client Component */}
      <LikeButton initialLikes={post.likes} postId={post.id} />
    </article>
  );
}

Đọc thêm: Nếu bạn đang gặp khó khăn với SSR, hãy xem hướng dẫn này về cách khắc phục Lỗi Hydration của Next.js.

Bạn cũng có thể thích

Advertisement

Câu hỏi thường gặp

Tôi có thể nhập Server Component bên trong Client Component không?

Không. Vì Client Components chạy trong trình duyệt, bất kỳ component nào chúng nhập cũng sẽ buộc phải chạy trong trình duyệt (và được bao gồm trong gói JS). Tuy nhiên, bạn có thể truyền Server Component dưới dạng prop children cho Client Component, giữ nguyên môi trường của Server Component.

Client Components có vẫn render trên máy chủ không?

Có! Trong các framework như Next.js, Client Components vẫn được pre-render trên máy chủ để tạo HTML ban đầu (SSR), nhưng chúng cũng được gửi đến máy khách để được hydrate với khả năng tương tác. Mặt khác, Server Components chỉ được render trên máy chủ và không bao giờ được hydrate trên máy khách.

Làm cách nào để chia sẻ trạng thái giữa Server và Client components?

Server Components là không trạng thái. Bạn có thể truyền dữ liệu tĩnh (như kết quả truy vấn cơ sở dữ liệu) xuống Client Component thông qua props. Nếu bạn cần trạng thái tương tác toàn cục (như giỏ hàng hoặc chủ đề người dùng), trạng thái đó phải được quản lý hoàn toàn trong cây Client Component bằng cách sử dụng các thư viện như Zustand, Jotai hoặc React Context.

Tìm hiểu sâu: Cơ chế cốt lõi

Khi chúng ta nhìn sâu hơn, các cơ chế cơ bản cho thấy sự tương tác phức tạp của các hệ thống. Trong phát triển hiện đại, việc hiểu các cơ chế này là điều phân biệt một người mới bắt đầu với một chuyên gia.

Hãy xem xét ví dụ thực tế này:

// A comprehensive example demonstrating advanced patterns
class ServiceManager {
  constructor() {
    this.services = new Map();
    this.initialized = false;
  }

  register(name, service) {
    if (this.services.has(name)) {
      throw new Error(`Service ${name} already registered`);
    }
    this.services.set(name, service);
  }

  async initializeAll() {
    this.initialized = true;
    for (const [name, service] of this.services) {
      if (typeof service.init === 'function') {
        await service.init();
      }
    }
  }

  get(name) {
    if (!this.initialized) {
      console.warn('Accessing services before initialization');
    }
    return this.services.get(name);
  }
}

Mô hình này đảm bảo rằng kiến trúc của chúng ta vẫn có thể mở rộng và mạnh mẽ ngay cả khi các yêu cầu kinh doanh thay đổi. Đây là một cách tiếp cận cơ bản mang lại lợi ích trong các ứng dụng quy mô lớn.

Ứng dụng và mở rộng quy mô trong thế giới thực

Việc triển khai điều này trong môi trường sản xuất đưa ra một loạt thách thức mới. Chúng ta phải tính đến tính đồng thời, quản lý trạng thái và rò rỉ bộ nhớ.

Ví dụ, khi xử lý các hệ thống thông lượng cao, mọi tối ưu hóa nhỏ đều có giá trị. Chúng ta thường dựa vào các công cụ phân tích hiệu suất để xác định các điểm nghẽn không rõ ràng trong quá trình phát triển cục bộ.

Sơ đồ trên minh họa một chiến lược triển khai điển hình trong đó ứng dụng của chúng ta mở rộng theo chiều ngang.

Kiểm tra kiến thức của bạn

Câu hỏi thường gặp

SSR pre-render các Client Components tương tác thành HTML ban đầu trên máy chủ, nhưng vẫn chuyển toàn bộ gói JavaScript đến trình duyệt để React có thể hydrate trạng thái và trình nghe sự kiện. RSC thực thi độc quyền trên máy chủ và truyền một định dạng UI chuyên biệt (payload RSC); các dependency và mã của nó không bao giờ được tải xuống trình duyệt máy khách, dẫn đến không có chi phí kích thước gói máy khách.
Có. Mặc dù Client Component không thể trực tiếp nhập Server Component, bạn có thể truyền Server Component dưới dạng child slot: <ClientWrapper><ServerPostContent /></ClientWrapper>. Server component thực thi ở backend và cây ảo đã render của nó được đưa vào ranh giới máy khách tương tác một cách liền mạch.
Server Actions là các hàm bất đồng bộ được đánh dấu bằng 'use server' thực thi an toàn trên máy chủ. Client Components tương tác có thể gọi chúng trực tiếp bên trong các biểu mẫu gửi hoặc trình xử lý sự kiện để thay đổi các bản ghi cơ sở dữ liệu, tự động kích hoạt tái xác thực các cây dữ liệu Server Component xung quanh.
Bằng cách giữ các dependency nặng (như thư viện ngày, trình tô sáng cú pháp và gói chuyển đổi dữ liệu) hoàn toàn trên máy chủ, RSC cắt giảm payload JavaScript của máy khách từ 30-70%. Ít thực thi script máy khách hơn giúp giải phóng luồng chính của trình duyệt, tăng tốc Largest Contentful Paint (LCP) và giảm đáng kể độ trễ Interaction to Next Paint (INP).
Các prop được truyền qua ranh giới phải có thể tuần tự hóa. Chuỗi, số, boolean, đối tượng thuần túy, mảng và các phần tử JSX được hỗ trợ. Bạn không thể truyền trực tiếp các hàm, thể hiện lớp, Symbol hoặc đối tượng Date mà không cần tuần tự hóa trước.
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement