[React Basics] Rendering Lists trong React: Cách làm chuẩn và tối ưu hiệu suất

Trong React, việc hiển thị danh sách dữ liệu là một trong những kỹ năng nền tảng và thiết yếu nhất mà bất kỳ nhà phát triển nào cũng phải nắm vững. Từ danh sách sản phẩm trong một trang thương mại điện tử, danh sách bài viết trên blog, cho đến một bảng tin mạng xã hội, tất cả đều quy về một kỹ thuật chung: render một danh sách.

Rendering Lists trong React: Cách làm chuẩn và tối ưu hiệu suất

Bài viết này sẽ dẫn bạn từ những bước cơ bản nhất đến các kỹ thuật tối ưu hóa hiệu năng, giúp bạn không chỉ "biết làm" mà còn "làm tốt nhất" khi xử lý danh sách trong React.

Render danh sách: "Phép thuật" của vòng lặp map

Hãy quên đi các vòng lặp for hay while truyền thống trong JavaScript. Với React, tư duy của chúng ta là khai báo (declarative). Chúng ta không ra lệnh cho máy tính "làm thế nào" để vẽ từng mục, mà chỉ cần mô tả "cái gì" cần được hiển thị. Và công cụ mạnh mẽ nhất cho việc này chính là phương thức map() của Array.

Phương thức map() duyệt qua từng phần tử của một mảng và trả về một mảng mới chứa kết quả từ việc thực thi một hàm trên mỗi phần tử đó. Trong React, mảng mới này chính là một danh sách các phần tử React (JSX) sẵn sàng để được render.

Ví dụ kinh điển:

Hãy tưởng tượng bạn có một mảng dữ liệu về các siêu anh hùng:

const heroes = [
  { id: 1, name: 'Iron Man' },
  { id: 2, name: 'Captain America' },
  { id: 3, name: 'Thor' },
]

Để render danh sách này trong một component, bạn chỉ cần "map" qua nó:

function HeroList() {
  const heroes = [
    { id: 1, name: 'Iron Man' },
    { id: 2, name: 'Captain America' },
    { id: 3, name: 'Thor' },
  ]

  return (
    <ul>
      {heroes.map((hero) => (
        <li key={hero.id}>{hero.name}</li>
      ))}
    </ul>
  )
}

Phân tích:

  • Chúng ta sử dụng dấu ngoặc nhọn {} để nhúng biểu thức JavaScript vào trong JSX.
  • heroes.map(...) sẽ duyệt qua mảng heroes.
  • Với mỗi hero trong mảng, chúng ta trả về một phần tử <li> chứa tên của anh ta.
  • Kết quả là React sẽ render một danh sách <ul> với ba mục <li> tương ứng.

Prop key: Không chỉ là một prop thông thường

Bạn có để ý đến prop key={hero.id} trong ví dụ trên không? Đây không phải là một chi tiết có thể bỏ qua. Prop key là yếu tố sống còn để React có thể nhận diện, cập nhật và tối ưu hóa việc render danh sách.

Khi một danh sách thay đổi (thêm, xóa, sắp xếp lại các phần tử), React cần một cách để xác định xem phần tử nào đã thay đổi, phần tử nào là mới và phần tử nào đã bị xóa. key chính là "chứng minh thư" của mỗi phần tử trong danh sách.

Tại sao key lại quan trọng đến vậy?

  1. Hiệu năng: Nhờ có key, React có thể sử dụng thuật toán "diffing" (so sánh) một cách hiệu quả. Thay vì phá hủy và tạo lại toàn bộ danh sách DOM mỗi khi có thay đổi, React chỉ cần di chuyển, cập nhật hoặc xóa những phần tử DOM thực sự có sự thay đổi. Điều này giúp tăng tốc độ render một cách đáng kể, đặc biệt với các danh sách lớn.
  2. Duy trì State: Nếu các mục trong danh sách của bạn là các component có state riêng (ví dụ: một checkbox đã được tích), việc cung cấp key ổn định đảm bảo rằng state sẽ được gắn liền với đúng phần tử đó ngay cả khi thứ tự danh sách thay đổi. Nếu không có key hoặc key không ổn định, bạn có thể gặp phải những lỗi rất khó lường về state.

Chọn key như thế nào cho đúng?

  • Tốt nhất: Sử dụng một ID duy nhất và ổn định từ dữ liệu của bạn (ví dụ: product.id, user.id). Đây là lựa chọn lý tưởng nhất.
  • Có thể chấp nhận (trong trường hợp đặc biệt): Nếu dữ liệu của bạn không có ID, bạn có thể tạo ra một ID tạm thời. Tuy nhiên, hãy đảm bảo ID này không thay đổi giữa các lần render.
  • Tuyệt đối tránh: Sử dụng index của mảng map((item, index) => <li key={index}>...) làm key. Đây là một "anti-pattern" (mẫu hình xấu) phổ biến. Tại sao? Vì nếu danh sách bị sắp xếp lại, hoặc một phần tử bị thêm/xóa ở đầu/giữa, index của các phần tử sẽ thay đổi, khiến React nhận diện sai và có thể dẫn đến lỗi hiển thị và hiệu năng kém. Chỉ sử dụng index làm key khi bạn chắc chắn 100% rằng danh sách đó là tĩnh và không bao giờ thay đổi thứ tự.

Các kỹ thuật nâng cao và Tình huống thực tế

1. Tách component con

Khi mỗi mục trong danh sách trở nên phức tạp, việc giữ tất cả logic trong một component duy nhất sẽ trở nên rối rắm. Đây là lúc chúng ta nên tách ra thành các component con.

// ListItem.js
function ListItem({ item }) {
  return (
    <li>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </li>
  )
}

// ParentList.js
function ParentList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  )
}

Lưu ý quan trọng: key phải được đặt ở component được lặp trong map (<ListItem />), chứ không phải bên trong component con (<li> trong ListItem.js).

2. Render có điều kiện trong danh sách

Đôi khi bạn chỉ muốn hiển thị những mục thỏa mãn một điều kiện nào đó. Bạn có thể kết hợp filter() với map().

function ActiveUsersList({ users }) {
  return (
    <ul>
      {users
        .filter((user) => user.isActive)
        .map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
    </ul>
  )
}

Hoặc render nội dung khác nhau dựa trên điều kiện ngay trong map():

{
  items.map((item) => (
    <li key={item.id}>
      {item.name}
      {item.isNew && <span> (Mới!)</span>}
    </li>
  ))
}

3. Xử lý danh sách rỗng

Một trải nghiệm người dùng tốt là khi ứng dụng của bạn thông báo cho họ biết khi không có dữ liệu để hiển thị.

function ItemList({ items }) {
  if (items.length === 0) {
    return <p>Không có sản phẩm nào để hiển thị.</p>
  }

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

4. Tối ưu hóa hiệu năng với danh sách lớn: "Windowing"

Khi bạn phải render hàng trăm, hàng nghìn, hay thậm chí hàng chục nghìn mục, việc render tất cả chúng cùng một lúc sẽ khiến ứng dụng của bạn trở nên cực kỳ chậm chạp. Giải pháp cho vấn đề này là kỹ thuật "windowing" hay "virtualized lists".

Ý tưởng rất đơn giản: chỉ render những mục đang thực sự hiển thị trong khung nhìn (viewport) của người dùng, cộng thêm một vài mục đệm ở trên và dưới. Khi người dùng cuộn, chúng ta sẽ thay thế các mục đã render bằng các mục mới.

Virtualized Scrolling

Các thư viện phổ biến giúp bạn dễ dàng triển khai kỹ thuật này bao gồm:

  • React Window
  • React Virtualized
  • TanStack Virtual (mới và hiện đại hơn)

Sử dụng các thư viện này có thể cải thiện đáng kể hiệu năng và trải nghiệm người dùng cho các ứng dụng có dữ liệu lớn.

Kết luận: Rendering Lists không phải chỉ để chạy được

Render danh sách là một công việc hàng ngày của lập trình viên React. Bằng cách nắm vững cách sử dụng map(), hiểu sâu sắc tầm quan trọng của key, và biết khi nào cần áp dụng các kỹ thuật nâng cao như tách component hay "windowing", bạn không chỉ đang viết code cho chạy được, mà còn đang xây dựng những ứng dụng nhanh, mượt mà và có khả năng mở rộng.

Chúc bạn thành công trên hành trình chinh phục React!

Bài viết liên quan

[React Basics] Thuộc tính key trong React: Hiểu rõ và sử dụng hiệu quả

Bạn đã thực sự hiểu về thuộc tính key trong React? Tìm hiểu vai trò, cách dùng hiệu quả và các ví dụ thực tế giúp code của bạn sạch và tối ưu hơn.

[React Basics] Props trong React: Khái niệm, cách dùng và ví dụ thực tế

Giải mã props trong ReactJS: từ khái niệm cơ bản đến cách truyền dữ liệu giữa các component một cách hiệu quả. Khám phá các ví dụ minh họa dễ hiểu để bạn áp dụng ngay.

[React Basics] React Handling Events: Những cách làm hiệu quả và tối ưu

Handling Events là một kỹ năng quan trọng trong React. Bài viết này sẽ giúp bạn hiểu rõ về cú pháp, cách truyền đối số và quản lý trạng thái khi làm việc với các sự kiện.

[React Basics] State trong React: Khái niệm, cách sử dụng và ví dụ chi tiết

Tìm hiểu khái niệm, cách khai báo và sử dụng State để quản lý dữ liệu động trong ứng dụng React của bạn. Xem ngay hướng dẫn chi tiết kèm ví dụ.