VirtualizedList** là component nền tảng (base) của FlatList và SectionList. Dùng trực tiếp khi cần **kiểm soát hoàn toàn** cách fetch/render item cho danh sách cực lớn (hàng nghìn, triệu item) hoặc dữ liệu không phải mảng đơn giản (pagination, infinite scroll phức tạp, virtualized grid).
Import
import { VirtualizedList, View, Text, StyleSheet, SafeAreaView } from 'react-native';
Cú pháp & Props bắt buộc
<VirtualizedList
data={DATA} // Dữ liệu (bất kỳ kiểu, truyền vào getItem/getItemCount)
getItem={getItem} // (data, index) => item - BẮT BUỘC
getItemCount={getItemCount} // (data) => number - BẮT BUỘC
renderItem={renderItem} // ({ item, index }) => ReactElement - BẮT BUỘC
keyExtractor={keyExtractor} // (item, index) => string - BẮT BUỘC
initialNumToRender={10} // Số item render lần đầu
// ... các props khác như FlatList
/>
Các props cốt lõi (Phải hiểu rõ)
| Prop | Chữ ký hàm | Mô tả |
getItem | (data, index) => item | Lấy item tại index từ data source. Cho phép data không phải mảng (Map, object, pagination cursor...). |
getItemCount | (data) => number | Tổng số item. Dùng cho scrollbar, scroll to end detection. |
renderItem | ({ item, index, separators }) => ReactElement | Render item. separators: { highlight, unhighlight, updateProps }. Ưu tiên separators.highlight()/unhighlight() cho touch feedback. |
keyExtractor | (item, index) => string | Key duy nhất cho reconciliation & caching. |
Ví dụ 1: VirtualizedList cơ bản (data mảng đơn giản)
import React from 'react';
import { VirtualizedList, View, Text, StyleSheet, SafeAreaView } from 'react-native';
const DATA = Array.from({ length: 10000 }, (_, i) => ({
id: i,
title: `Item ${i + 1}`,
}));
const getItem = (data, index) => data[index];
const getItemCount = data => data.length;
const keyExtractor = item => item.id.toString();
const Item = ({ title }) => (
{title}
);
const VirtualizedListExample = () => (
}
keyExtractor={keyExtractor}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={5}
/>
);
const styles = StyleSheet.create({
container: { flex: 1 },
item: { padding: 20, borderBottomWidth: 1, borderColor: '#eee' },
title: { fontSize: 16 },
});
export default VirtualizedListExample;
Ví dụ 2: VirtualizedList với Pagination (Load more)
import React, { useState, useCallback, useEffect } from 'react';
import { VirtualizedList, View, Text, StyleSheet, SafeAreaView, ActivityIndicator } from 'react-native';
const PaginatedList = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1);
// Mock API
const fetchPage = async (pageNum) => {
setLoading(true);
await new Promise(r => setTimeout(r, 500));
const newItems = Array.from({ length: 20 }, (_, i) => ({
id: (pageNum - 1) * 20 + i,
title: `Item ${(pageNum - 1) * 20 + i + 1}`,
}));
setData(prev => [...prev, ...newItems]);
setHasMore(pageNum < 5); // Giả lập 5 trang
setLoading(false);
};
useEffect(() => { fetchPage(1); }, []);
const getItem = (data, index) => data[index];
const getItemCount = data => data.length + (hasMore ? 1 : 0); // +1 cho loading item
const keyExtractor = (item, index) => item ? item.id.toString() : `loading-${index}`;
const renderItem = ({ item, index }) => {
if (!item) {
// Loading item cuối
return (
{loading && }
{!loading && !hasMore && Hết dữ liệu}
);
}
return {item.title};
};
const onEndReached = useCallback(() => {
if (!loading && hasMore) {
setPage(p => p + 1);
fetchPage(page + 1);
}
}, [loading, hasMore, page]);
return (
}
/>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
item: { padding: 20, borderBottomWidth: 1, borderColor: '#eee' },
loadingContainer: { padding: 20, alignItems: 'center' },
endText: { color: '#999' },
footerLoader: { padding: 20 },
});
export default PaginatedList;
VirtualizedList vs FlatList vs SectionList
| Đặc điểm | FlatList | SectionList | VirtualizedList |
| Data input | Array | Array of sections | Bất kỳ (cung cấp getItem/getItemCount) |
| Section header | Không | Có | Tự implement |
| Độ phức tạp | Thấp | Trung bình | Cao |
| Dùng khi | Danh sách đơn giản | Danh sách có nhóm | Data phức tạp, pagination custom, non-array source, grid virtualized |
| Hiệu suất | Tốt | Tốt | Tối ưu nhất (khi config đúng) |
Performance Tips
- getItemLayout: Nếu item chiều cao cố định, cung cấp để bỏ qua measurement:
getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index })}.
- React.memo cho Item component.
- initialNumToRender: Chỉ render item nhìn thấy đầu tiên.
- maxToRenderPerBatch,
windowSize, updateCellsBatchingPeriod: Tinh chỉnh batch rendering.
- useNativeDriver cho animation trong item (nếu có).
- Tránh inline function/arrow function trong props -> dùng
useCallback.
Khi nào KHÔNG nên dùng VirtualizedList trực tiếp?
- Danh sách đơn giản, dữ liệu là mảng -> Dùng
FlatList.
- Danh sách có section/header -> Dùng
SectionList.
- Chỉ dùng khi FlatList/SectionList **không đáp ứng được** yêu cầu data source hoặc render logic đặc thù.