Xử lý 100,000 dòng dữ liệu trong React mượt mà với react-window

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Khi danh sách dữ liệu trở thành “cơn ác mộng” của trình duyệt

Đã bao giờ bạn thử render một cái table hay danh sách tầm 10,000 dòng và thấy Chrome bắt đầu “đứng hình” chưa? Trải nghiệm lúc đó cực kỳ tệ. Thao tác cuộn chuột trở nên giật lag, click vào một item thì phải mất cả giây trình duyệt mới phản hồi.

Vấn đề thực tế không nằm ở React. Thủ phạm chính là cách trình duyệt xử lý DOM. Hãy tính thử: nếu bạn render 10,000 dòng, mỗi dòng chứa 5 thẻ HTML, trình duyệt phải gánh tới 50,000 DOM nodes. Chỉ cần một thay đổi state nhỏ, trình duyệt sẽ phải tính toán lại layout (reflow) và vẽ lại (repaint) đống node khổng lồ đó, khiến FPS tụt thê thảm.

Tôi từng xử lý một dashboard hiển thị log server thời gian thực. Ban đầu, team dùng .map() để hiển thị dữ liệu vì nghĩ vài nghìn dòng không thành vấn đề. Kết quả là sau 30 phút vận hành, số lượng log tăng vọt, RAM Chrome chiếm dụng gần 2GB và tab bị crash hoàn toàn. Bài học ở đây rất rõ ràng: Đừng bao giờ bắt trình duyệt hiển thị những thứ mà người dùng không nhìn thấy.

Kỹ thuật Virtualization: Chỉ vẽ những gì bạn thấy

Nguyên lý của Virtualization (hay Windowing) rất đơn giản. Thay vì dựng toàn bộ 10,000 dòng, chúng ta chỉ render đúng những dòng nằm trong khung nhìn (viewport) và thêm một vài dòng đệm (buffer) để đảm bảo cuộn mượt.

Khi bạn cuộn xuống, các phần tử cũ trôi ra ngoài sẽ bị gỡ bỏ khỏi DOM ngay lập tức. Cùng lúc đó, các phần tử mới sẽ được chèn vào. Nhờ vậy, số lượng DOM nodes luôn duy trì ở mức tối thiểu, ví dụ chỉ khoảng 20-30 node, bất kể tập dữ liệu của bạn lớn đến mức nào.

Trong cộng đồng React, react-window là thư viện chuẩn chỉnh nhất để giải quyết việc này. Nó là bản rút gọn siêu nhẹ của react-virtualized, tập trung tối đa vào hiệu suất và cực kỳ dễ cài đặt.

Thực hành: Tích hợp react-window vào dự án

Trước tiên, hãy thêm thư viện vào project của bạn:

npm install react-window
# Hoặc
yarn add react-window

1. Sử dụng FixedSizeList cho chiều cao cố định

Nếu các dòng trong danh sách của bạn có độ cao bằng nhau (ví dụ: 50px), đây là lựa chọn tối ưu nhất.

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style, data }) => (
  // Style cực kỳ quan trọng để định vị vị trí các dòng
  <div style={style} className="border-b flex items-center px-4">
    <span>Dòng {index + 1}: {data[index].name}</span>
  </div>
);

const MyList = ({ items }) => (
  <List
    height={500}        // Chiều cao khung nhìn (px)
    itemCount={items.length} 
    itemSize={50}       // Chiều cao mỗi dòng (px)
    width="100%"        
    itemData={items}    
  >
    {Row}
  </List>
);

Lưu ý nhỏ: prop style trong component Row là bắt buộc. react-window sử dụng position: absolute để đặt các dòng vào đúng vị trí khi cuộn. Nếu thiếu nó, tất cả các dòng sẽ bị đè lên nhau ở trên cùng.

2. Xử lý danh sách có chiều cao linh hoạt (VariableSizeList)

Trường hợp nội dung mỗi dòng dài ngắn khác nhau, bạn hãy dùng VariableSizeList. Lúc này, itemSize sẽ nhận vào một function để tính toán kích thước dựa theo index.

import { VariableSizeList as List } from 'react-window';

const getItemSize = index => (index % 2 === 0 ? 50 : 100);

const MyVariableList = ({ items }) => (
  <List
    height={500}
    itemCount={items.length}
    itemSize={getItemSize}
    width="100%"
  >
    {({ index, style }) => (
      <div style={style}>Dòng {index} với độ cao tùy biến</div>
    )}
  </List>
);

Mẹo tối ưu hiệu suất thực tế

Để đạt được độ mượt 60 FPS khi cuộn, bạn cần lưu ý hai điểm “xương máu” sau:

Sử dụng React.memo cho Row

Dù Virtualization đã giảm số lượng node, nhưng nếu component Row tính toán quá nặng, việc cuộn vẫn sẽ bị khựng. Hãy bọc Row trong React.memo kết hợp với helper areEqual từ thư viện để tránh re-render thừa.

import React, { memo } from 'react';
import { areEqual } from 'react-window';

const Row = memo(({ index, style, data }) => {
  return <div style={style}>{data[index].label}</div>;
}, areEqual);

Tự động điều chỉnh kích thước với AutoSizer

react-window yêu cầu chiều cao và chiều rộng bằng con số cụ thể. Tuy nhiên, giao diện web thường cần responsive. Bạn nên dùng kèm react-virtualized-auto-sizer để danh sách tự lấp đầy không gian cha.

Khi nào không nên dùng react-window?

Dù rất mạnh mẽ, nhưng bạn đừng nên lạm dụng nó trong các trường hợp:

  • Danh sách ngắn dưới 200 phần tử: DOM vẫn xử lý tốt, dùng .map() sẽ giúp code gọn gàng hơn.
  • Cần tính năng tìm kiếm mặc định (Ctrl + F): Vì các dòng không hiển thị sẽ không tồn tại trong DOM, trình duyệt không thể tìm thấy chúng.
  • Giao diện quá phức tạp, không theo cấu trúc hàng hoặc lưới.

Tổng kết

Tối ưu React không chỉ quanh quẩn với useMemo. Thay đổi cách tương tác với DOM mới là chìa khóa cho dữ liệu lớn. Với react-window, bạn có thể xử lý hàng chục nghìn dòng mà RAM vẫn “xanh”, trình duyệt vẫn mượt. Hãy thử áp dụng ngay vào project để thấy sự khác biệt về tốc độ phản hồi nhé!

Share: