Урок по треку React Native 7 мин

Списки: FlatList, виртуализация, FlashList

FlatList, SectionList, keyExtractor, getItemLayout, виртуализация и FlashList.

Лента, каталог, чат, история заказов: почти в каждом приложении есть длинный список, и это самое частое место, где RN-приложение тормозит. Поэтому вопрос «как оптимизировать FlatList» звучит на каждом втором собесе.

Почему не ScrollView

ScrollView рендерит всех детей сразу. Тысяча карточек значит тысяча нативных представлений в памяти и долгий первый рендер. Списки в RN виртуализированы: в дереве только видимые элементы и запас вокруг, остальные заменены пустым местом.

FlatList

<FlatList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={renderItem}
  onEndReached={loadMore}
  onEndReachedThreshold={0.5}
  refreshing={isRefreshing}
  onRefresh={refresh}
  ListEmptyComponent={<Empty />}
/>
  • keyExtractor даёт стабильный ключ. Индекс в ключе ломает состояние элементов при вставке в начало, как и в вебе.
  • onEndReached вызывается, когда до конца осталось меньше onEndReachedThreshold видимой высоты (0.5 это половина экрана). Может вызваться несколько раз подряд, поэтому нужна защита от повторной загрузки.
  • SectionList для групп с заголовками, horizontal для горизонтальных лент, numColumns для сетки.

Настройки виртуализации

ПропЧто делает
initialNumToRenderсколько элементов отрисовать сразу (по умолчанию 10)
windowSizeразмер окна отрисовки в экранах (по умолчанию 21: 10 сверху, 10 снизу и видимый)
maxToRenderPerBatchсколько элементов дорисовывать за один проход
removeClippedSubviewsотсоединять невидимые нативные представления
getItemLayoutзаранее известная высота: список не меряет элементы и умеет прыгать к индексу

Уменьшение windowSize экономит память, но при быстрой прокрутке появляются пустые места. Это компромисс, а не волшебная настройка.

getItemLayout

Если высота элементов одинакова, список может вычислить положение любого элемента без рендера:

const ITEM = 72;
const getItemLayout = (_: unknown, index: number) => ({
  length: ITEM,
  offset: ITEM * index,
  index,
});

Это ускоряет прокрутку и делает надёжным scrollToIndex. Если высота разная, неверный getItemLayout хуже его отсутствия: элементы будут прыгать.

Правила быстрого элемента

  1. Мемоизированный элемент. const Row = memo(function Row({ item, onPress }) {...}). Без этого каждое обновление списка перерисует все видимые строки.
  2. Стабильные колбэки. renderItem и обработчики через useCallback. Стрелка, создаваемая в каждом рендере, ломает memo.
  3. Лёгкая разметка. Меньше вложенных View, без тяжёлых вычислений в рендере.
  4. Картинки нужного размера и с кешем (expo-image или react-native-fast-image), а не фото по 4 МБ.
  5. extraData. FlatList это PureComponent: если элементы зависят от состояния вне data (например, выбранный id), передайте его в extraData, иначе список не перерисуется.

FlashList

@shopify/flash-list не создаёт новые компоненты при прокрутке, а переиспользует (recycling) уже созданные, подставляя новые данные, как RecyclerView на Android и UICollectionView на iOS. Отсюда меньше работы для JS и меньше пустых мест при быстрой прокрутке. Следствие переиспользования: локальное состояние внутри элемента (useState) может «переехать» к другим данным, поэтому его сбрасывают по смене id или хранят снаружи. Вторая версия FlashList работает только на новой архитектуре и сама измеряет элементы.

Частые ошибки

  • ScrollView с map по сотням элементов.
  • Ключ по индексу.
  • Инлайновый renderItem и не мемоизированные строки.
  • onEndReached без флага загрузки: дубли страниц.
  • Список внутри ScrollView той же ориентации: виртуализация ломается, RN предупреждает об этом.

Что спросят на собесе

  • Как работает виртуализация и чем FlatList лучше ScrollView.
  • Как оптимизировать медленный список: пропсы и мемоизация.
  • Зачем getItemLayout и extraData.
  • Как сделать бесконечную подгрузку без дублей.
  • Чем FlashList отличается от FlatList и какой подвох у переиспользования.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.