Các bảng và danh sách siêu lớn có thể làm chậm đáng kể hiệu suất của trang web. Tính năng ảo hoá có thể giúp bạn!
react-window
là một thư viện cho phép hiển thị hiệu quả các danh sách lớn.
Dưới đây là ví dụ về một danh sách chứa 1000 hàng được hiển thị bằng react-window
. Hãy thử cuộn nhanh nhất có thể.
Tại sao thông tin này hữu ích?
Đôi khi, bạn cần hiển thị một bảng hoặc danh sách lớn chứa nhiều hàng. Việc tải từng mục trên một danh sách như vậy có thể ảnh hưởng đáng kể đến hiệu suất.
Ảo hoá danh sách (list virtualization) hay còn gọi là "cửa sổ" (windowing) là khái niệm chỉ hiển thị những nội dung mà người dùng nhìn thấy. Số lượng phần tử được kết xuất lúc đầu là một tập hợp con rất nhỏ của toàn bộ danh sách và "cửa sổ" của nội dung hiển thị sẽ di chuyển khi người dùng tiếp tục cuộn. Điều này giúp cải thiện cả hiệu suất kết xuất và cuộn của danh sách.
Các nút DOM thoát khỏi "cửa sổ" sẽ được tái chế hoặc ngay lập tức được thay thế bằng các phần tử mới hơn khi người dùng cuộn xuống danh sách. Điều này giúp số lượng tất cả các phần tử được kết xuất cụ thể theo kích thước của cửa sổ.
react-window
react-window
là một thư viện nhỏ của bên thứ ba giúp bạn dễ dàng tạo danh sách ảo hoá trong ứng dụng. Thư viện này cung cấp một số API cơ sở có thể dùng cho nhiều loại danh sách và bảng.
Trường hợp nên sử dụng danh sách có kích thước cố định
Sử dụng thành phần FixedSizeList
nếu bạn có một danh sách dài, một chiều gồm các mục có kích thước bằng nhau.
import React from 'react';
import { FixedSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const ListComponent = () => (
<FixedSizeList
height={500}
width={500}
itemSize={120}
itemCount={items.length}
>
{Row}
</FixedSizeList>
);
export default ListComponent;
- Thành phần
FixedSizeList
chấp nhận thuộc tínhheight
,width
vàitemSize
để kiểm soát kích thước của các mục trong danh sách. - Hàm hiển thị các hàng được truyền dưới dạng phần tử con đến
FixedSizeList
. Bạn có thể truy cập thông tin chi tiết về một mục cụ thể bằng đối sốindex
(items[index]
). - Tham số
style
cũng được truyền vào phương thức kết xuất hàng mà phải được đính kèm vào phần tử hàng. Các mục trong danh sách được định vị tuyệt đối với các giá trị chiều cao và chiều rộng được chỉ định cùng dòng và thông sốstyle
chịu trách nhiệm cho việc này.
Ví dụ về Glitch được trình bày ở phần trước của bài viết này là một ví dụ về thành phần FixedSizeList
.
Trường hợp nên sử dụng danh sách có kích thước biến đổi
Sử dụng thành phần VariableSizeList
để hiển thị danh sách các mục có kích thước khác nhau. Thành phần này hoạt động giống như danh sách có kích thước cố định, nhưng
yêu cầu một hàm cho thuộc tính itemSize
thay vì một giá trị cụ thể.
import React from 'react';
import { VariableSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const getItemSize = index => {
// return a size for items[index]
}
const ListComponent = () => (
<VariableSizeList
height={500}
width={500}
itemCount={items.length}
itemSize={getItemSize}
>
{Row}
</VariableSizeList>
);
export default ListComponent;
Phần nhúng sau đây cho thấy ví dụ về thành phần này.
Hàm kích thước mục được truyền đến thuộc tính itemSize
sẽ tạo ngẫu nhiên chiều cao hàng trong ví dụ này. Tuy nhiên, trong một ứng dụng thực tế, phải có logic thực tế xác định kích thước của từng mục. Tốt nhất là bạn nên tính toán các kích thước này dựa trên dữ liệu hoặc lấy từ một API.
Lưới
react-window
cũng hỗ trợ việc ảo hoá các danh sách hoặc lưới nhiều chiều. Trong ngữ cảnh này, "cửa sổ" của nội dung hiển thị sẽ thay đổi khi người dùng cuộn theo chiều ngang và chiều dọc.
Tương tự, bạn có thể sử dụng cả thành phần FixedSizeGrid
và VariableSizeGrid
tuỳ thuộc vào việc kích thước của các mục danh sách cụ thể có thể thay đổi hay không.
- Đối với
FixedSizeGrid
, API cũng tương tự nhưng cần thể hiện chiều cao, chiều rộng và số lượng mục cho cả cột và hàng. - Đối với
VariableSizeGrid
, bạn có thể thay đổi cả chiều rộng cột và chiều cao hàng bằng cách truyền vào các hàm thay vì giá trị cho các thuộc tính tương ứng.
Hãy xem tài liệu để xem các ví dụ về lưới ảo hoá.
Tải từng phần khi cuộn
Nhiều trang web cải thiện hiệu suất bằng cách chờ tải và hiển thị các mục trong một danh sách dài cho đến khi người dùng cuộn xuống. Kỹ thuật này, thường được gọi là "tải vô hạn", sẽ thêm các nút DOM mới vào danh sách khi người dùng cuộn qua một ngưỡng nhất định gần cuối. Mặc dù cách này tốt hơn so với việc tải tất cả các mục trong danh sách cùng một lúc, nhưng cuối cùng vẫn sẽ điền DOM bằng hàng nghìn mục nhập hàng nếu người dùng đã cuộn qua nhiều mục như vậy. Điều này có thể dẫn đến kích thước DOM quá lớn, bắt đầu ảnh hưởng đến hiệu suất bằng cách làm chậm các phép tính kiểu và đột biến DOM.
Sơ đồ sau đây có thể giúp tóm tắt điều này:
Cách tốt nhất để giải quyết vấn đề này là tiếp tục sử dụng một thư viện như react-window
để duy trì một "cửa sổ" nhỏ gồm các phần tử trên một trang, nhưng cũng tải từng phần mới hơn khi người dùng cuộn xuống. Một gói riêng biệt, react-window-infinite-loader
, giúp bạn có thể thực hiện việc này với react-window
.
Hãy xem xét đoạn mã sau đây cho thấy ví dụ về trạng thái được quản lý trong thành phần App
mẹ.
import React, { Component } from 'react';
import ListComponent from './ListComponent';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // instantiate initial list here
moreItemsLoading: false,
hasNextPage: true
};
this.loadMore = this.loadMore.bind(this);
}
loadMore() {
// method to fetch newer entries for the list
}
render() {
const { items, moreItemsLoading, hasNextPage } = this.state;
return (
<ListComponent
items={items}
moreItemsLoading={moreItemsLoading}
loadMore={this.loadMore}
hasNextPage={hasNextPage}
/>
);
}
}
export default App;
Phương thức loadMore
được truyền vào ListComponent
con chứa danh sách trình tải vô hạn. Điều này rất quan trọng vì trình tải vô hạn cần kích hoạt lệnh gọi lại để tải thêm các mục sau khi người dùng cuộn qua một điểm nhất định.
Dưới đây là giao diện của ListComponent
hiển thị danh sách:
import React from 'react';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from "react-window-infinite-loader";
const ListComponent = ({ items, moreItemsLoading, loadMore, hasNextPage }) => {
const Row = ({ index, style }) => (
{/* define the row component using items[index] */}
);
const itemCount = hasNextPage ? items.length + 1 : items.length;
return (
<InfiniteLoader
isItemLoaded={index => index < items.length}
itemCount={itemCount}
loadMoreItems={loadMore}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
height={500}
width={500}
itemCount={itemCount}
itemSize={120}
onItemsRendered={onItemsRendered}
ref={ref}
>
{Row}
</FixedSizeList>
)}
</InfiniteLoader>
)
};
export default ListComponent;
Trong đó, thành phần FixedSizeList
được gói trong InfiniteLoader
.
Các thuộc tính được chỉ định cho trình tải là:
isItemLoaded
: Phương thức kiểm tra xem một mục nhất định đã tải hay chưaitemCount
: Số mục trong danh sách (hoặc dự kiến)loadMoreItems
: Lệnh gọi lại trả về một lời hứa phân giải thành dữ liệu bổ sung cho danh sách
Thuộc tính kết xuất được dùng để trả về một hàm mà thành phần danh sách sử dụng để hiển thị.
Cả hai thuộc tính onItemsRendered
và ref
đều là các thuộc tính cần được truyền vào.
Sau đây là ví dụ về cách hoạt động của tính năng tải vô hạn với danh sách được ảo hoá.
Việc cuộn xuống danh sách có thể vẫn như cũ, nhưng giờ đây, một yêu cầu sẽ được thực hiện để truy xuất 10 người dùng từ API người dùng ngẫu nhiên mỗi khi bạn cuộn gần đến cuối danh sách. Tất cả việc này được thực hiện trong khi chỉ hiển thị một "cửa sổ" kết quả tại một thời điểm.
Bằng cách kiểm tra index
của một mục nhất định, trạng thái tải khác có thể được hiển thị cho một mục tuỳ thuộc vào việc liệu yêu cầu đã được thực hiện cho các mục mới hơn hay chưa và mục đó vẫn đang tải.
Ví dụ:
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
Tràn màn hình
Vì các mục trong danh sách ảo hoá chỉ thay đổi khi người dùng cuộn, nên khoảng trống có thể nhấp nháy nhanh khi các mục mới hơn sắp hiển thị. Bạn có thể thử cuộn nhanh bất kỳ ví dụ nào trước đó trong hướng dẫn này để nhận thấy điều này.
Để cải thiện trải nghiệm người dùng của danh sách ảo hoá, react-window
cho phép bạn quét nhiều lần các mục bằng thuộc tính overscanCount
. Điều này cho phép bạn xác định số lượng mục bên ngoài "cửa sổ" hiển thị để hiển thị mọi lúc.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
hoạt động cho cả thành phần FixedSizeList
và VariableSizeList
và có giá trị mặc định là 1. Tuỳ thuộc vào kích thước của danh sách cũng như kích thước của từng mục, việc quét nhiều lần không chỉ một mục có thể giúp ngăn chặn tình trạng không gian trống xuất hiện chớp nhoáng khi người dùng cuộn. Tuy nhiên, việc quét quá nhiều mục nhập có thể ảnh hưởng tiêu cực đến hiệu suất. Mục đích của việc sử dụng danh sách ảo hoá là giảm thiểu số mục mà người dùng có thể thấy tại một thời điểm bất kỳ, vì vậy, hãy cố gắng giảm thiểu số mục được quét quá mức.
Đối với FixedSizeGrid
và VariableSizeGrid
, hãy sử dụng các thuộc tính overscanColumnsCount
và overscanRowsCount
để kiểm soát số lượng cột và hàng tương ứng để quét nhiều lần.
Kết luận
Nếu bạn không biết nên bắt đầu ảo hoá danh sách và bảng trong ứng dụng từ đâu, hãy làm theo các bước sau:
- Đo lường hiệu suất kết xuất và cuộn. Bài viết này cho biết cách sử dụng đồng hồ FPS trong Công cụ của Chrome cho nhà phát triển để khám phá mức độ hiệu quả của việc hiển thị các mục trên danh sách.
- Thêm
react-window
cho mọi danh sách hoặc lưới dài đang ảnh hưởng đến hiệu suất. - Nếu có một số tính năng nhất định không được hỗ trợ trong
react-window
, hãy cân nhắc sử dụngreact-virtualized
nếu bạn không thể tự thêm chức năng này. - Gói danh sách ảo hoá của bạn bằng
react-window-infinite-loader
nếu bạn cần tải lười các mục khi người dùng cuộn. - Sử dụng thuộc tính
overscanCount
cho danh sách và thuộc tínhoverscanColumnsCount
vàoverscanRowsCount
cho lưới để ngăn nội dung trống xuất hiện. Đừng quét quá nhiều mục nhập vì điều này sẽ ảnh hưởng tiêu cực đến hiệu suất.