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.
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ảngheroes
.- 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?
- 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. - 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ấpkey
ổ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ặckey
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àmkey
. Đâ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ụngindex
làmkey
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.
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!