Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Component VirtualizedList trong React Native: Hiệu suất cho danh sách lớn


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õ)

PropChữ ký hàmMô tả
getItem(data, index) => itemLấy item tại index từ data source. Cho phép data không phải mảng (Map, object, pagination cursor...).
getItemCount(data) => numberTổng số item. Dùng cho scrollbar, scroll to end detection.
renderItem({ item, index, separators }) => ReactElementRender item. separators: { highlight, unhighlight, updateProps }. Ưu tiên separators.highlight()/unhighlight() cho touch feedback.
keyExtractor(item, index) => stringKey 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ểmFlatListSectionListVirtualizedList
Data inputArrayArray of sectionsBất kỳ (cung cấp getItem/getItemCount)
Section headerKhôngCóTự implement
Độ phức tạpThấpTrung bìnhCao
Dùng khiDanh sách đơn giảnDanh sách có nhómData phức tạp, pagination custom, non-array source, grid virtualized
Hiệu suấtTốtTốtTố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ù.