Command Palette

Search for a command to run...

Cách dùng FlatList trong React Native: Hiển thị danh sách không giật lag

Cách dùng FlatList trong React Native

Trong quá trình xây dựng ứng dụng, bạn sẽ liên tục phải đối mặt với việc hiển thị danh sách: Bảng tin mạng xã hội, danh sách sản phẩm, lịch sử giao dịch, hay danh bạ điện thoại...

Ở những bài trước, chúng ta đã biết đến ScrollView để làm cho nội dung có thể vuốt lên xuống. Tuy nhiên, nếu bạn dùng ScrollView để hiển thị một danh sách có chứa hàng trăm, hay hàng ngàn mục, ứng dụng của bạn sẽ lập tức trở thành một "thảm họa" về hiệu năng: Điện thoại nóng rực, vuốt giật lag, và cuối cùng là văng app (crash).

Tại sao lại như vậy? Và làm thế nào để các ứng dụng lớn có thể cuộn hàng ngàn bài viết một cách mượt mà? Câu trả lời nằm ở "vũ khí tối thượng" mang tên FlatList.

1. Tại sao ScrollView lại thất bại trước dữ liệu lớn?

Để hiểu sự vĩ đại của FlatList, bạn cần hiểu điểm yếu chí mạng của ScrollView.

Khi bạn truyền 1.000 mục dữ liệu vào ScrollView, nó sẽ ra lệnh cho điện thoại: "Hãy vẽ (render) toàn bộ 1.000 mục này ra bộ nhớ ngay lập tức, dù hiện tại màn hình chỉ hiển thị được 5 mục". Việc ép hệ thống phải tải và xử lý hàng ngàn hình ảnh, văn bản cùng lúc sẽ làm cạn kiệt bộ nhớ (RAM) nhanh chóng.

FlatList ra đời để giải quyết triệt để vấn đề này bằng một cơ chế thông minh gọi là Cơ chế ảo hóa (Virtualization) / Lazy Loading:

ScrollView vs FlatList

  • FlatList chỉ vẽ những mục đang thực sự hiển thị trên màn hình và một vài mục chuẩn bị xuất hiện khi bạn vuốt.
  • Khi bạn vuốt qua một mục và nó biến mất khỏi màn hình, FlatList sẽ lập tức xóa nó khỏi bộ nhớ để nhường chỗ cho mục mới.
  • Kết quả: Dù danh sách của bạn có 10 mục hay 100.000 mục, dung lượng RAM ứng dụng tiêu thụ gần như không thay đổi!

2. Cấu trúc cốt lõi của một FlatList

Để một FlatList hoạt động, nó cần bạn cung cấp đúng 3 thông tin quan trọng nhất (thông qua 3 thuộc tính - props):

  1. data: Nguồn dữ liệu (Một mảng các object).
  2. renderItem: Bản vẽ thiết kế. Bạn muốn mỗi dòng trong danh sách trông như thế nào?
  3. keyExtractor: Mã định danh. Cách để FlatList phân biệt dòng này với dòng khác (giống như số CMND/CCCD).

FlatList trong React Native

Thực hành: Xây dựng cẩm nang nhân vật

Giả sử chúng ta đang làm một ứng dụng bách khoa toàn thư, tổng hợp các bài viết hướng dẫn chi tiết cho một loạt các nhân vật. Chúng ta có một danh sách dữ liệu đầu vào như sau:

const CHAMPION_DATA = [
  { id: '1', name: 'Master Yi', role: 'Sát Thủ / Đấu Sĩ', avatar: 'https://link-to-yi.png' },
  { id: '2', name: 'Ngộ Không', role: 'Đấu Sĩ', avatar: 'https://link-to-wukong.png' },
  { id: '3', name: 'Lee Sin', role: 'Đấu Sĩ / Sát Thủ', avatar: 'https://link-to-leesin.png' },
  { id: '4', name: 'Katarina', role: 'Sát Thủ', avatar: 'https://link-to-katarina.png' },
  // ... Hàng trăm nhân vật khác
]

Bây giờ, hãy đưa dữ liệu này vào FlatList:

import React from 'react'
import { View, Text, Image, FlatList, StyleSheet, TouchableOpacity } from 'react-native'

export default function ChampionListScreen() {
  // Hàm renderItem: Khai báo giao diện cho TỪNG mục trong danh sách
  // Lưu ý: FlatList sẽ truyền vào một object có chứa thuộc tính 'item'
  const renderChampionCard = ({ item }) => (
    <TouchableOpacity style={styles.card}>
      <View style={styles.infoContainer}>
        <Text style={styles.name}>{item.name}</Text>
        <Text style={styles.role}>{item.role}</Text>
      </View>
    </TouchableOpacity>
  )

  return (
    <View style={styles.screen}>
      <Text style={styles.headerTitle}>Danh Sách Nhân Vật</Text>

      <FlatList
        data={CHAMPION_DATA} // 1. Truyền mảng dữ liệu vào
        renderItem={renderChampionCard} // 2. Truyền hàm vẽ giao diện vào
        keyExtractor={(item) => item.id} // 3. Trích xuất ID làm khóa duy nhất
        // Các thuộc tính phụ trợ làm đẹp:
        showsVerticalScrollIndicator={false} // Ẩn thanh cuộn bên phải
        contentContainerStyle={{ paddingBottom: 20 }} // Thêm khoảng trống ở cuối danh sách
      />
    </View>
  )
}

const styles = StyleSheet.create({
  screen: { flex: 1, backgroundColor: '#f5f6fa', paddingHorizontal: 15 },
  headerTitle: { fontSize: 24, fontWeight: 'bold', marginVertical: 20, color: '#2f3640' },
  card: {
    backgroundColor: '#ffffff',
    padding: 15,
    borderRadius: 10,
    marginBottom: 15,
    flexDirection: 'row',
    alignItems: 'center',
    elevation: 2, // Bóng đổ Android
    shadowColor: '#000', // Bóng đổ iOS
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
  },
  infoContainer: { marginLeft: 15 },
  name: { fontSize: 18, fontWeight: 'bold', color: '#192a56' },
  role: { fontSize: 14, color: '#7f8fa6', marginTop: 5 },
})

3. SectionList: Khi bạn cần nhóm dữ liệu

Đôi khi, danh sách của bạn cần được chia thành từng nhóm có tiêu đề riêng (như danh bạ điện thoại chia theo chữ cái A, B, C). React Native cung cấp một người anh em của FlatList mang tên SectionList.

Với SectionList, cấu trúc dữ liệu đầu vào của bạn phải thay đổi một chút. Nó phải là một mảng các đối tượng, trong đó mỗi đối tượng đại diện cho một nhóm và BẮT BUỘC phải có thuộc tính data.

const GROUPED_DATA = [
  {
    title: 'Sát Thủ', // Tiêu đề nhóm
    data: ['Katarina', 'Master Yi'], // Danh sách thuộc nhóm
  },
  {
    title: 'Đấu Sĩ',
    data: ['Lee Sin', 'Ngộ Không'],
  },
]

Khi sử dụng, bạn sẽ cung cấp thêm một prop có tên là renderSectionHeader để vẽ tiêu đề cho từng nhóm:

<SectionList
  sections={GROUPED_DATA}
  keyExtractor={(item, index) => item + index}
  renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
  renderSectionHeader={({ section: { title } }) => <Text style={styles.header}>{title}</Text>}
/>

4. Bí kíp tối ưu hóa FlatList (Advanced)

Nếu danh sách của bạn cực kỳ phức tạp (mỗi dòng chứa nhiều ảnh chất lượng cao và các tính toán nặng), bạn có thể gặp hiện tượng vuốt bị "trắng màn hình" một khoảnh khắc trước khi dữ liệu kịp hiển thị. Hãy bổ sung các thuộc tính sau vào FlatList để tăng tốc độ:

  • initialNumToRender: Số lượng mục cần vẽ ngay lập tức khi mở màn hình. (Ví dụ: initialNumToRender={10}). Đặt vừa đủ lấp đầy màn hình đầu tiên để app khởi động nhanh hơn.
  • windowSize: Kích thước của "vùng đệm" bộ nhớ. Mặc định là 21 (10 màn hình phía trên, 10 màn hình phía dưới và màn hình hiện tại). Nếu app giật, hãy giảm con số này xuống (ví dụ: windowSize={5}) để giải phóng RAM mạnh tay hơn.
  • getItemLayout: Nếu các mục trong danh sách của bạn CÓ CHIỀU CAO CỐ ĐỊNH giống nhau, hãy sử dụng hàm này. Nó giúp FlatList bỏ qua bước tính toán kích thước phức tạp, giúp tốc độ cuộn tăng lên chóng mặt.

Kết luận: Dữ liệu lớn không còn là nỗi sợ

Hãy biến quy tắc này thành phản xạ: Cứ có danh sách (dù ít hay nhiều), hãy ưu tiên sử dụng FlatList. Chỉ dùng ScrollView cho các trang nội dung tĩnh như bài viết blog hay trang giới thiệu cài đặt.

Chúc mừng bạn! Tới đây, bạn đã nắm vững toàn bộ nghệ thuật xây dựng giao diện từ Core Components, Flexbox, Xử lý sự kiện cho đến FlatList. Ứng dụng của bạn hiện tại đã rất xịn xò trên một màn hình duy nhất.

Nhưng làm thế nào để chuyển từ trang chủ (Home) sang trang chi tiết (Detail)? Làm sao để có thanh menu dưới đáy màn hình? Hãy chuẩn bị tinh thần bước vào bài học tiếp theo, nơi chúng ta sẽ hô biến các màn hình rời rạc thành một ứng dụng hoàn chỉnh!

Bài viết liên quan

Cách gọi API trong React Native: Hướng dẫn Fetch Data và xử lý JSON

Hướng dẫn chi tiết cách gọi API trong React Native. Nắm vững kỹ thuật sử dụng fetch, axios kết hợp với useEffect, useState để tải và hiển thị dữ liệu server mượt mà.

Hướng dẫn cài đặt môi trường React Native & Expo trong 15 phút

Bạn sợ cài đặt môi trường lập trình mobile phức tạp? Hướng dẫn chi tiết cách dùng Expo Go, Node.js để chạy app React Native ngay lập tức.

Stack Navigation trong Expo Router: Chuyển trang & Truyền tham số

Hướng dẫn chi tiết cách chuyển trang và truyền tham số bằng Stack Navigation trong React Native, sử dụng Expo Router.

Event Handling trong React Native: Sự khác biệt giữa Pressable và TouchableOpacity

Hướng dẫn cách bắt sự kiện chạm, nhấn trong React Native. Tìm hiểu vì sao không nên dùng thẻ Button và cách thay thế bằng Pressable, TouchableOpacity.