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 useCallback và useMemo
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 initialNumToRender và maxToRenderPerBatch
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ó id và title. 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.







