Giới thiệu

FlatList là thành phần danh sách ảo hoá mạnh mẽ trong React Native, nhưng nếu không cấu hình đúng sẽ gây ra hiện tượng lag, giảm trải nghiệm người dùng. Bài viết này sẽ phân tích nguyên nhân và đưa ra các kỹ thuật tối ưu hiệu năng, đồng thời minh hoạ bằng mã TypeScript.

Nguyên nhân gây lag trong FlatList

Render quá nhiều mục cùng lúc

FlatList mặc định render một số mục ban đầu ( initialNumToRender ) và tự động tải thêm khi người dùng cuộn. Khi dữ liệu lớn và các mục có cấu trúc phức tạp, việc render lại toàn bộ sẽ tiêu tốn CPU và bộ nhớ.

Key không ổn định

React dựa vào key để tái sử dụng các phần tử. Nếu keyExtractor trả về giá trị không duy nhất hoặc thay đổi liên tục, React sẽ phải hủy và tạo lại các component, gây mất hiệu năng.

Các kỹ thuật tối ưu

Sử dụng getItemLayout

Khi các mục có chiều cao cố định, khai báo getItemLayout giúp FlatList tính toán vị trí mà không cần đo kích thước mỗi lần render.

<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={item => item.id.toString()}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

Memo hoá component item

Sử dụng React.memo để tránh render lại các mục không thay đổi. Kết hợp với areEqual tùy chỉnh để kiểm tra sâu hơn.

const ListItem = React.memo(({item, onPress}) => {
  return (
    <TouchableOpacity onPress={() => onPress(item.id)}>
      <Text>{item.title}</Text>
    </TouchableOpacity>
  );
}, (prevProps, nextProps) => prevProps.item.id === nextProps.item.id);

Sử dụng useCallbackuseMemo

Hàm xử lý sự kiện và dữ liệu phụ thuộc vào danh sách nên được bọc bằng useCallback để giữ tham chiếu ổn định, tránh truyền hàm mới vào renderItem mỗi lần render.

const onPressItem = useCallback((id: number) => {
  console.log('Selected', id);
}, []);

const renderItem = useCallback(({item}) => (
  <ListItem item={item} onPress={onPressItem} />
), [onPressItem]);

Điều chỉnh initialNumToRendermaxToRenderPerBatch

Giảm số lượng mục render ban đầu và mỗi batch giúp giảm tải CPU trong các thiết bị yếu. Tuy nhiên cần cân bằng để tránh hiện tượng trắng màn khi cuộn.

  • initialNumToRender: số mục render ngay khi danh sách xuất hiện.
  • maxToRenderPerBatch: số mục tối đa render trong một batch.
  • windowSize: số batch phía trước và phía sau được giữ trong bộ nhớ.

Một ví dụ thực tế

Dưới đây là một component FlatList hoàn chỉnh, áp dụng các kỹ thuật trên. Dữ liệu giả định là một mảng các đối tượng có idtitle. Mỗi mục có chiều cao cố định 70 pixel.

import React, {useCallback, useMemo} from 'react';
import {FlatList, Text, TouchableOpacity, View, StyleSheet} from 'react-native';

interface Item {
  id: number;
  title: string;
}

const DATA: Item[] = Array.from({length: 1000}, (_, i) => ({
  id: i + 1,
  title: `Item ${i + 1}`,
}));

const ITEM_HEIGHT = 70;

const ListItem = React.memo(({item, onPress}: {item: Item; onPress: (id: number) => void}) => (
   onPress(item.id)}>
    {item.title}
  
), (prev, next) => prev.item.id === next.item.id);

const App = () => {
  const onPressItem = useCallback((id: number) => {
    console.log('Selected', id);
  }, []);

  const renderItem = useCallback(({item}: {item: Item}) => (
    
  ), [onPressItem]);

  const keyExtractor = useCallback((item: Item) => item.id.toString(), []);

  const getItemLayout = useCallback((_data: Item[] | null, index: number) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);

  return (
    
      
    
  );
};

const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: '#fff'},
  item: {height: ITEM_HEIGHT, justifyContent: 'center', paddingHorizontal: 16, borderBottomWidth: 1, borderColor: '#eee'},
  title: {fontSize: 16},
});

export default App;

Kết luận

Áp dụng getItemLayout, memo hoá item, và tối ưu các tham số batch sẽ giảm đáng kể thời gian render và tiêu thụ bộ nhớ, mang lại trải nghiệm mượt mà trên cả thiết bị cấp thấp. Để nắm vững toàn bộ quy trình phát triển ứng dụng di động chuyên nghiệp, Tham khảo khóa học "Lập trình App Mobile với React Native + TypeScript" tại đây.