Chào mừng "làn gió mới": Server Components!
Nếu bạn đã và đang làm việc với React, chắc hẳn đã quá quen thuộc với việc mọi thứ đều chạy trên trình duyệt của người dùng (client-side). Nhưng với sự ra đời của React Server Components (RSC), React đã có một bước tiến lớn, cho phép chúng ta chạy các component ngay trên server. Điều này mở ra nhiều cánh cửa mới về hiệu suất và trải nghiệm người dùng, nhưng đồng thời cũng khiến không ít anh em developer bối rối: Khi nào thì dùng Client Components truyền thống, và khi nào nên "đón đầu" Server Components?
Đừng lo lắng! Bài viết này sẽ giúp bạn "giải mã" sự khác biệt cốt lõi, cách thức hoạt động và những trường hợp sử dụng tối ưu cho từng loại component. Hãy cùng bắt đầu nhé!
Client Components: Người bạn cũ quen thuộc
Client Components là những component mà chúng ta đã dùng bấy lâu nay. Đúng như tên gọi, chúng chạy hoàn toàn trên trình duyệt của người dùng.
Đặc điểm nổi bật:
- Tương tác: Xử lý các sự kiện như click, nhập liệu, hover.
- State & Effects: Quản lý trạng thái bằng
useStatevà các tác vụ phụ bằnguseEffect. - Truy cập Browser APIs: Có thể tương tác với
localStorage,window,navigatorvà DOM. - Bundled JavaScript: Code của Client Components được gửi về trình duyệt, góp phần vào kích thước gói JavaScript tổng thể của ứng dụng.
Ví dụ điển hình:
Hãy tưởng tượng một bộ đếm đơn giản, nơi người dùng có thể click để tăng giá trị. Đây chắc chắn là một Client Component vì nó cần tương tác trực tiếp và quản lý state cục bộ.
// app/components/MyClientComponent.tsx "use client"; // Đánh dấu đây là Client Component import { useState } from 'react'; export default function MyClientComponent() { const [count, setCount] = useState(0); return ( <div> <p>Bạn đã click {count} lần.</p> <button onClick={() => setCount(count + 1)}>Click tôi!</button> </div> ); }Server Components: Làn gió mới hiệu năng cao
Server Components là "ngôi sao mới nổi" trong hệ sinh thái React. Chúng chạy trên server, render ra HTML tĩnh (hoặc một định dạng trung gian) và gửi về client.
Đặc điểm nổi bật:
- Không tương tác (mặc định): Không có
useState,useEffecthay các trình xử lý sự kiện trực tiếp trên client. - Truy cập trực tiếp Backend: Có thể truy vấn database, đọc file hệ thống hoặc gọi các API backend một cách trực tiếp và an toàn mà không cần API layer riêng.
- Giảm JavaScript Bundle Size: Code của Server Components không bao giờ được gửi về trình duyệt, giúp giảm đáng kể kích thước gói JavaScript ban đầu.
- Cải thiện hiệu suất tải trang: Render nhanh chóng trên server, giúp trang hiển thị nội dung sớm hơn (First Contentful Paint, Largest Contentful Paint).
- Tăng cường bảo mật: Giữ logic nhạy cảm (như khóa API, truy vấn database) hoàn toàn trên server, tránh lộ ra client.
- Tối ưu SEO: Nội dung được render sẵn trên server, thân thiện hơn với các công cụ tìm kiếm.
Ví dụ điển hình:
Một component hiển thị danh sách sản phẩm hoặc bài viết blog, nơi dữ liệu được lấy từ database. Server Component là lựa chọn hoàn hảo vì nó có thể fetch dữ liệu trực tiếp và không cần tương tác client-side.
// app/components/MyServerComponent.tsx // Mặc định là Server Component trừ khi có "use client" import { getProductsFromDatabase } from '../lib/data'; // Giả định hàm fetch data export default async function MyServerComponent() { const products = await getProductsFromDatabase(); // Fetch data trực tiếp trên server return ( <div> <h3>Sản phẩm nổi bật</h3> <ul> {products.map((product) => ( <li key={product.id}> {product.name} - Giá: {product.price} </li> ))} </ul> </div> ); }Khi nào dùng Client Components?
Hãy sử dụng Client Components khi ứng dụng của bạn cần:
- Tương tác người dùng: Bất cứ khi nào bạn cần xử lý
onClick,onChange,onSubmit, v.v. - Quản lý trạng thái động: Sử dụng
useState,useReducerđể quản lý trạng thái thay đổi trên client. - Tác vụ phụ trên client: Dùng
useEffectcho các tác vụ như fetch dữ liệu sau khi component mount, subscriptions, hoặc tương tác với DOM trực tiếp. - Truy cập Browser APIs: Khi cần dùng
localStorage,sessionStorage,window,navigator. - Các thư viện chỉ chạy trên client: Một số thư viện biểu đồ, bản đồ, hoặc thư viện UI phức tạp yêu cầu môi trường trình duyệt.
Khi nào dùng Server Components?
Server Components là lựa chọn tuyệt vời trong các trường hợp sau:
- Lấy dữ liệu (Data Fetching): Truy vấn database hoặc gọi các API bên ngoài. Đây là một trong những use case mạnh mẽ nhất của RSC.
- Logic nhạy cảm: Khi bạn có các đoạn mã chứa thông tin bảo mật (API keys) hoặc logic kinh doanh quan trọng mà không muốn lộ ra client.
- Giảm JavaScript Bundle Size: Khi bạn muốn gửi càng ít JavaScript về client càng tốt để cải thiện tốc độ tải trang.
- Nội dung tĩnh hoặc ít thay đổi: Các trang blog, trang giới thiệu sản phẩm, trang hiển thị thông tin...
- SEO: Đảm bảo nội dung chính của trang có sẵn trong HTML được gửi về từ server, giúp các công cụ tìm kiếm dễ dàng index hơn.
- Tối ưu hiệu suất ban đầu: Giúp trang hiển thị nội dung nhanh chóng, đặc biệt quan trọng cho trải nghiệm người dùng đầu tiên (First Load).
Sức mạnh tổng hợp: Kết hợp RSC và Client Components
Điều tuyệt vời nhất là bạn không cần phải chọn một trong hai! RSC và Client Components được thiết kế để làm việc cùng nhau một cách liền mạch.
- RSC có thể render Client Components: Một Server Component có thể import và render một Client Component. Đây là cách phổ biến để thêm tương tác vào các trang được render trên server.
- Truyền props từ Server xuống Client: Server Component có thể fetch dữ liệu và truyền nó dưới dạng props cho Client Component con.
Lưu ý quan trọng: Client Components KHÔNG THỂ import Server Components. Nếu bạn cố gắng làm điều này, bạn sẽ gặp lỗi.
Ví dụ kết hợp:
Server Component lấy danh sách sản phẩm, sau đó truyền từng sản phẩm xuống một Client Component AddToCartButton để xử lý việc thêm vào giỏ hàng (một hành động tương tác).
// app/components/ProductList.tsx (Server Component) import { getProductsFromDatabase } from '../lib/data'; import AddToCartButton from './AddToCartButton'; // Client Component export default async function ProductList() { const products = await getProductsFromDatabase(); return ( <div> <h2>Danh sách sản phẩm</h2> {products.map((product) => ( <div key={product.id}> <h3>{product.name}</h3> <p>Giá: {product.price}</p> <AddToCartButton productId={product.id} /> {/* Truyền props xuống Client Component */} </div> ))} </div> ); } // app/components/AddToCartButton.tsx (Client Component) "use client"; import { useState } from 'react'; export default function AddToCartButton({ productId }) { const [isAdding, setIsAdding] = useState(false); const handleAddToCart = () => { setIsAdding(true); // Giả lập gọi API thêm vào giỏ hàng setTimeout(() => { alert(`Đã thêm sản phẩm ${productId} vào giỏ hàng!`); setIsAdding(false); }, 1000); }; return ( <button onClick={handleAddToCart} disabled={isAdding}> {isAdding ? 'Đang thêm...' : 'Thêm vào giỏ'} </button> ); }Kết luận
React Server Components không phải là để thay thế hoàn toàn Client Components, mà là để bổ sung và mở rộng khả năng của React. Nắm vững sự khác biệt và biết khi nào nên sử dụng loại nào sẽ giúp bạn xây dựng các ứng dụng nhanh hơn, hiệu quả hơn, an toàn hơn và mang lại trải nghiệm người dùng tốt hơn.
Hãy nhớ rằng, chìa khóa là sử dụng đúng công cụ cho đúng mục đích. Kết hợp hài hòa cả Client và Server Components sẽ giúp bạn tận dụng tối đa sức mạnh của React trong kỷ nguyên mới này!