FlashList v2 в React Native: Оптимизация на списъци през 2026
Практическо ръководство за миграция към FlashList v2 в React Native: изисквания за Новата архитектура, getItemType recycling, типизиран TypeScript код и реални метрики от fintech приложение.
FlashList v2 е пълно пренаписване на списъчния компонент на Shopify за React Native, който през 2026 работи изцяло на JavaScript слоя върху Новата архитектура, не изисква estimatedItemSize и замества FlatList в продукционни приложения без загуба на функционалност. В този материал ще разгледам как да мигрирам продукционен фийд от FlatList или FlashList v1 към v2, кои настройки наистина имат значение за производителността, и какви капани съм срещал в реални fintech приложения с хиляди редове с транзакции.
FlashList v2 работи само с Новата архитектура. От React Native 0.82 (октомври 2025) старият bridge е премахнат, така че това вече не е ограничение.
Свойството estimatedItemSize е премахнато. v2 измерва клетките точно и не се нуждае от догадки.
Библиотеката е JS-only: няма нативни модули, което улеснява интеграцията в монорепо и премахва цял клас build проблеми.
Автоматичното задържане на визуалната позиция (maintainVisibleContentPosition) вече е включено по подразбиране, което е критично за чатове и live фийдове.
За хетерогенни списъци getItemType задава отделни recycling пулове и на практика удвоява скоростта на рендер.
Мигрирането от v1 отнема средно 30–60 минути на екран; ползата в JS thread натоварването може да е 5–10× при дълги списъци.
Какво е FlashList v2 и защо е важно през 2026
FlashList v2 е списъчен компонент от Shopify, който беше публикуван в края на 2025 като пълно пренаписване на оригиналната библиотека. Първата версия наследяваше RecyclerListView и разчиташе на нативни модули за корекции на layout-а. Това работеше добре, но налагаше на разработчика да предоставя estimatedItemSize и понякога влизаше в конфликт с определени анимации на UI thread-а.
Втората версия отхвърля тези компромиси. Тя е проектирана за Новата архитектура (Fabric renderer, TurboModules и JSI) и използва синхронния layout API, за да измери всяка клетка точно, без предварителни догадки. Резултатът е списък, който в моята практика с fintech приложение (списък с транзакции по 50 000 реда на месец) държи JS thread натоварването под 15% дори при бързо скролиране, докато старият FlatList достигаше 90%+.
Втората важна промяна е, че v2 е изцяло на JavaScript. Няма pod install, няма Gradle плъгин, няма autolinking изненади при ъпгрейд на React Native. За платформен екип, който поддържа Expo managed workflow заедно с bare workflow, това означава един по-малко потенциален източник на build failure. Всеки, който е чел статията ми за оптимизация на времето за стартиране на React Native, знае колко скъпо струва един лош нативен модул при cold start.
FlashList v2 срещу FlatList: сравнителна таблица
Когато обяснявам разликата на нови колеги, обикновено рисувам следната таблица. Тя показва защо през 2026 FlatList все още има място, но не и в основни продуктови списъци.
Характеристика
FlashList v2
FlatList (RN 0.82)
Механизъм на рендер
Cell recycling (пул от изгледи)
Пресъздаване на всеки item
Изисква estimatedItemSize
Не
N/A
Нативни модули
Няма (JS-only)
Няма
Съвместимост
Само Нова архитектура
И двете архитектури
JS thread натоварване при скрол на 1000+ реда
10–15%
60–90%
Празни клетки (blank cells)
Много редки
Чести при сложни items
Автоматично maintainVisibleContentPosition
Да, по подразбиране
Не (опционално)
Masonry / многоколонни layout-и
Вградено
Само чрез numColumns
Sticky headers
Да
Да
Bundle размер (gzip)
~14 KB
Част от core
Извод: за списък от 20 елемента, който никога няма да порасне, FlatList е по-простият избор. За всичко останало (новинарски фийд, списък с чат съобщения, каталог с продукти, история на транзакции) FlashList v2 е разумният default.
Инсталация и изисквания за Новата архитектура
Първо се уверявам, че приложението е на React Native 0.79 или по-нов, защото v2 разчита на API-та на Fabric, които станаха стабилни там. Ако все още държите newArchEnabled=false, това е моментът да го премахнете. От версия 0.82 старият bridge беше премахнат напълно, а флагът се игнорира. Ако тъкмо започвате прехода, вижте разбора ми на миграцията към Fabric и TurboModules.
Самата инсталация е една команда:
npm install @shopify/flash-list@^2.0.0
# или
yarn add @shopify/flash-list@^2.0.0
# или в Expo проект
npx expo install @shopify/flash-list
За разлика от v1 не се налага да пипате Podfile, build.gradle или да пускате pod install. Ако мигрирате от v1, деинсталирайте старата версия, изтрийте ios/Pods и node_modules, след което пуснете npx expo prebuild --clean при използване на Expo. Това е и моментът да проверите с npx react-native config, че не са останали autolinking записи от старата версия.
Миграция от FlashList v1 към v2 стъпка по стъпка
Ето playbook-а, който използвах за миграцията на около 40 екрана в един fintech продукт. Средно време на екран: 40 минути, включително code review. Честно казано, най-дългата част беше пренаписването на E2E тестовете, а не самият списъчен код.
Премахнете estimatedItemSize, това е основната разлика. TypeScript ще ви покаже къде се използва.
Ако сте задавали estimatedListSize или estimatedFirstItemOffset, премахнете и тях.
Заменете overrideItemLayout с getItemType (за списъци с различни типове items).
Проверете дали използвате onBlankArea. Този callback е премахнат, защото v2 практически не оставя празни клетки.
Ако имате DrawDistance проп, той се задава сега през drawDistance с default стойност, която обикновено не се налага да променяте.
Пуснете E2E тестовете за скрол поведение. v2 има по-точен scrollToIndex, който може да разкрие грешки в тестове, разчитали на старото приблизително поведение.
Забележете, че getItemType не е задължителен. v2 работи и без него. Но ако имате повече от един тип клетка (напр. заглавие на дата плюс ред на транзакция), той включва отделни recycling пулове и на практика удвоява скоростта на рендер.
Типизирани списъци с TypeScript и generics
Работя в екипи, където всеки native мост и всеки списък трябва да е type-safe. FlashList v2 приема generic параметър за типа на елементите и това позволява да пишем renderItem без as касти.
Двете конкретни неща, които правя винаги: (1) фиксирам generic-а изрично като FlashList<Transaction>, за да не разчитам на инференс от data; (2) декларирам renderItem извън компонента и с ListRenderItem<T> типа, за да получа стабилна референция и да не пресъздавам функцията при всеки render. Комбинирано със useCallback за обработчиците на събития, това елиминира голяма част от излишните re-render-и.
Оптимизация с getItemType и рециклиране на клетки
Рециклирането е сърцето на FlashList. Когато потребителят скролва, компонентът не пресъздава изгледа за нов item, а вместо това взима вече изграден изглед, който е излязъл от екрана, и просто актуализира текста и props-ите. Проблемът е, че ако имате различни по размер и структура клетки (например date header срещу transaction row), един и същ recycling пул би принуждавал изгледа постоянно да сменя layout-а си, което е скъпо.
Решението е getItemType. Тази функция връща стринг или число за всеки item и FlashList държи отделен recycling пул за всяка стойност. Резултатът е, че headers се рециклират само от headers, а rows само от rows. Официалната документация на Shopify описва подробно кога да използвате getItemType и как да измервате ефекта му.
В fintech приложението, за което говоря, добавянето на getItemType свали времето за първи paint при връщане към списъка от 240ms на 90ms (измерено с React DevTools Profiler). Ефектът е най-голям при списъци с 3+ различни типа клетки. Ако имате един-единствен тип, спокойно оставете го, защото v2 върши същата работа и без явна конфигурация.
Втора техника е onLoad callback-ът, който v2 добави. Той се вика, когато първата партида клетки е рендерирана, и е добро място да измерите TTI (time to interactive) на екрана.
FlashList v2 предлага вграден Masonry layout, тоест Pinterest-подобното подреждане, при което клетките имат различна височина, но се позиционират в най-краткия наличен колонен слот. В v1 това изискваше отделен компонент MasonryFlashList. В v2 се активира с проп:
Хоризонталните списъци (типични за carousels от продукти или истории) са тривиални, задавате horizontal. Sticky headers работят чрез stickyHeaderIndices с масив от индекси, точно както в FlatList. Ако секцията ви има истинска йерархия (заглавия плюс групи от items), може да предпочетете SectionList, но FlashList с stickyHeaderIndices обикновено е по-производителен.
Често срещани проблеми и как да ги избегнем
Ето списък с грешки, които сме отстранявали в code review, подредени по честота. Голяма част от тях съм правил и аз лично, преди да ги хвана в профилировача.
Изтичане на inline функции в renderItem. Всеки () => handlePress(item.id) вътре в renderItem създава нова функция при всеки скрол. Използвайте useCallback или изнесете обработчика в дъщерния компонент, който сам чете item.id.
Промяна на data референцията при всеки render. Ако правите data={items.filter(...)} в JSX, филтрираният масив е нов при всеки render и FlashList трябва да преизчисли всичко. Мемоизирайте с useMemo.
Забравен keyExtractor. Работи и без него, но производителността при вмъкване или изтриване на items пада значително.
Използване на index в keyExtractor. При пренареждане на данните това чупи рециклирането. Винаги връщайте стабилно id.
Прекалено сложни items. Ако един ред на списъка има 30+ nested компоненти, разгледайте профилирането първо. FlashList не може да компенсира скъп render в клетката.
Липсваща конфигурация на изображения. Ако рендерите снимки, конфигурирайте FastImage или новия expo-image с прогресивно зареждане, иначе бавната мрежа ще запуши JS thread-а въпреки бързия списък.
Ако сте наблюдавали регресии след миграцията, първо изключете getItemType и вижте дали проблемът остава. В около 20% от случаите виждам погрешно категоризиране (напр. връщане на undefined за някои items), което води до непредвидимо рециклиране. Аз хванах точно този бъг в снабдителска история миналия месец: типът беше базиран на item.category, но една партида идваше без категория и цялата секция мигаше при всеки скрол.
Често задавани въпроси
Работи ли FlashList v2 на старата архитектура на React Native?
Не. FlashList v2 е проектиран изрично за Новата архитектура (Fabric и JSI) и няма да компилира на приложения със стария bridge. Ако сте на React Native 0.82 или по-нов, старият bridge вече не съществува. За по-стари версии използвайте FlashList v1.7.x.
Нужен ли ми е все още estimatedItemSize във FlashList v2?
Не. Пропът е премахнат от API-то. v2 измерва всяка клетка точно още при първия render чрез синхронен layout API на Fabric, което елиминира нуждата от предварителни оценки и премахва цял клас грешки от неправилно зададен размер.
По-бърз ли е FlashList v2 от FlatList?
Да, при списъци с повече от 50 елемента с реална сложност разликата е значителна. В моите измервания JS thread натоварването при скрол пада от 60–90% (FlatList) до 10–15% (FlashList v2), а blank cells практически изчезват. При съвсем прости списъци с фиксирани малки данни разликата не си струва миграцията.
Как FlashList рециклира клетките си?
FlashList поддържа пул от вече изградени изгледи. Когато item излезе от екрана, изгледът не се унищожава, а се задържа и се преизползва за нов item, който влиза в екрана. Само props-ите се актуализират, не се създава нов React node. Чрез getItemType можете да разделите пула по типове клетки.
Мога ли да използвам FlashList v2 с Expo?
Да, напълно. FlashList v2 е JS-only библиотека, така че работи и в managed, и в bare Expo workflow без нужда от custom development client. За Expo SDK 51 и по-нови просто пуснете npx expo install @shopify/flash-list и започвайте.
Профилиране и оптимизация на cold start в React Native, от Hermes bytecode през inline requires до Fabric и native splash. Traces, цифри и работещ код за 2026.
Стъпка по стъпка миграция на React Native приложение към Fabric и TurboModules: активиране в Android и iOS, чести грешки и измерими печалби в производителността.