אם אתם מפתחים אפליקציית React Native ב-2026 ויש בה רשימה עם יותר מכמה עשרות פריטים, ה-FlatList הוותיק כמעט תמיד יהיה צוואר הבקבוק הראשון שלכם בביצועים. ה-JS thread נעמס, מסגרות נופלות, וחללים לבנים צצים בזמן גלילה (בטח נתקלתם בזה לפחות פעם אחת). FlashList v2 של Shopify, שהושק בשנה האחרונה כשכתוב מלא מהיסוד, פותר כמעט את כל הבעיות האלה — אבל הוא מביא איתו דרישות והתנהגויות חדשות שחייבים להכיר לפני שצוללים פנימה.
במדריך הזה נעבור על בדיוק מה חדש ב-v2, למה הוא מהיר משמעותית מ-FlatList, איך מבצעים הגירה מ-v1 או מ-FlatList, ומה הטעויות הנפוצות שגורמות לחוסר יציבות בפרודקשן. גילוי נאות: אני עברתי את המסלול הזה באפליקציה פנימית לפני חודשיים, ואני מבטיח לכם שזה שווה כל דקה.
למה FlatList כבר לא מספיקה ב-2026
FlatList מבוססת על VirtualizedList: היא מעבירה פריטים שיצאו מהתצוגה דרך mount/unmount מלא. המשמעות? React חייב לבצע reconciliation על עץ קומפוננטות שלם בכל פעם שפריט חדש נכנס למסך. במכשירי אנדרואיד עם זיכרון נמוך, או ברשימות עם פריטים מורכבים (תמונות, אנימציות, ממים עם וידאו), ה-JS thread יכול להיתקע מעל 90% ניצולת — מה שמוביל לגלילה לא חלקה, ובמקרים קיצוניים ל-ANR.
FlashList, לעומת זאת, משתמשת בcell recycling. הגישה הזו כמעט זהה ל-UICollectionView ב-iOS ול-RecyclerView באנדרואיד: במקום להרוס ולבנות מחדש קומפוננטות, היא מחזיקה מאגר קבוע של instances וממחזרת אותן עם נתונים חדשים. במדידות של Shopify על אפליקציית הסוחר שלהם, המעבר הוריד את ניצולת ה-JS thread מ-90%+ לפחות מ-10%, וחיסל לגמרי קריסות מסוג Out-of-Memory ברשימות גדולות. לא רע, מה?
מה חדש ב-FlashList v2
ובכן, בואו נאמר את זה ברור: FlashList v2 הוא לא עדכון מינורי. זה שכתוב מלא שנבנה במפורש עבור הארכיטקטורה החדשה של React Native, עם מספר שינויים שמשנים את הדרך שבה אתם כותבים רשימות:
- אין יותר הערכות גדלים — ה-prop
estimatedItemSizeהוסר לגמרי. FlashList v2 מחשבת גדלים דינמית דרך מדידות סינכרוניות שה-Fabric מאפשר. - פתרון JS-only — אין יותר תלויות נייטיביות. כל הלוגיקה עברה ל-JavaScript, מה שמקל על תחזוקה ומשפר משמעותית את התמיכה ב-Web.
- Adaptive render window — אלגוריתם שמתחשב במהירות ובכיוון הגלילה, במקום חלון רינדור קבוע.
- גלילה בדיוק פיקסל —
scrollToIndexו-scrollToItemמתקנים את עצמם פרוגרסיבית עד שהם מגיעים למיקום מדויק. - רשימות אופקיות דינמיות — פריטים יכולים להיות בכל גודל, והרשימה מתאימה את עצמה אוטומטית.
- maintainVisibleContentPosition מופעל כברירת מחדל — מה שפותר סוף סוף את בעיית ה-jumps הידועה בצ'אטים ובפידים.
- תמיכה ב-RTL — קריטי עבורנו שמפתחים אפליקציות בעברית וערבית.
FlashList v2 לעומת FlatList: השוואה מפורטת
| תכונה | FlatList | FlashList v2 |
|---|---|---|
| אסטרטגיית רינדור | Virtualization (mount/unmount) | Cell recycling |
| הערכות גדלים | לא נדרשות | לא נדרשות (הוסרו ב-v2) |
| תמיכה בארכיטקטורה | ישנה + חדשה | חדשה בלבד |
| תלויות נייטיביות | אין | אין (JS-only) |
| חללים לבנים בגלילה מהירה | נפוצים | כמעט נעלמים |
| שמירת 60 FPS עם פריטים כבדים | בעייתית | יציבה |
| תמיכה ב-Masonry | חיצונית בלבד | מובנית (prop) |
| תמיכה ב-Web | טובה | טובה מאוד |
דרישת חובה: הארכיטקטורה החדשה
זו הנקודה הקריטית ביותר להבנה לפני שמתחילים, אז שימו לב. FlashList v2.x פשוט לא ירוץ על הארכיטקטורה הישנה. נקודה. אם האפליקציה שלכם עדיין על Paper (הארכיטקטורה הישנה), יש לכם שתי אפשרויות:
- להישאר עם FlashList v1.x לעת עתה (עם
estimatedItemSizeותמיכה בשתי הארכיטקטורות). - לבצע את המעבר ל-Fabric + TurboModules. ב-React Native 0.76 ומעלה הארכיטקטורה החדשה היא ברירת מחדל, כך שמרבית הפרויקטים החדשים כבר שם.
בדיקה מהירה: חפשו ב-app.json או ב-app.config.js את הערך newArchEnabled: true, או ודאו שבפרויקט Expo SDK 53+ לא השבתם אותה במפורש.
התקנה ושימוש בסיסי
ההתקנה ב-v2 פשוטה יותר, כי כבר אין שלב linking נייטיבי:
npm install @shopify/flash-list@^2.0.0
# או
yarn add @shopify/flash-list@^2.0.0
שימוש בסיסי נראה כך:
import React, { useCallback } from 'react';
import { View, Text } from 'react-native';
import { FlashList } from '@shopify/flash-list';
type Article = { id: string; title: string; summary: string };
export function ArticleList({ articles }: { articles: Article[] }) {
const renderItem = useCallback(({ item }: { item: Article }) => (
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: '600' }}>{item.title}</Text>
<Text style={{ color: '#666' }}>{item.summary}</Text>
</View>
), []);
return (
<FlashList
data={articles}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
);
}
שימו לב שאין יותר estimatedItemSize. FlashList כבר מזהה את הגדלים לבד, וזה מרגיש כמעט מאגי בפעם הראשונה שרואים את זה.
מדריך הגירה מ-FlashList v1 ל-v2
אם כבר יש לכם FlashList v1 בפרויקט, הנה רשימת השינויים המדויקת שחייבים לבצע. אני ממליץ לעבור עליהם לפי הסדר — זה יחסוך לכם באגים מוזרים בהמשך.
1. הסירו את כל ה-props שהוסרו
// v1 - להסיר
<FlashList
data={data}
renderItem={renderItem}
estimatedItemSize={80} // הוסר
estimatedListSize={{ height: 600, width: 400 }} // הוסר
estimatedFirstItemOffset={0} // הוסר
onBlankArea={onBlank} // הוסר
disableHorizontalListHeightMeasurement // הוסר
disableAutoLayout // הוסר
/>
// v2 - נקי
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
2. עדכנו את overrideItemLayout — רק span
ב-v1, overrideItemLayout תמך גם בשינוי span וגם בהגדרת layout.size. ב-v2 התמיכה ב-size הוסרה לחלוטין:
// v1
overrideItemLayout={(layout, item) => {
layout.span = item.span;
layout.size = 120; // הוסר ב-v2
}}
// v2
overrideItemLayout={(layout, item) => {
layout.span = item.span; // רק span
}}
3. החליפו את MasonryFlashList ב-prop masonry
הקומפוננטה MasonryFlashList הופסקה. במקום זאת משתמשים ב-FlashList עם ה-prop החדש:
// v1
import { MasonryFlashList } from '@shopify/flash-list';
<MasonryFlashList
data={photos}
renderItem={renderPhoto}
numColumns={3}
estimatedItemSize={200}
/>
// v2
import { FlashList } from '@shopify/flash-list';
<FlashList
data={photos}
renderItem={renderPhoto}
numColumns={3}
masonry
optimizeItemArrangement // חדש: ממזער פערי גובה בין עמודות
/>
שימו לב ש-getColumnFlex כבר לא נתמך. אם השתמשתם בו כדי לקבוע שעמודה מסוימת תהיה רחבה יותר, תצטרכו להשיג את האפקט דרך overrideItemLayout עם layout.span.
4. עדכנו את טיפוס ה-ref
// v1
import { FlashList } from '@shopify/flash-list';
const listRef = useRef<FlashList<Article>>(null);
// v2
import { FlashList, FlashListRef } from '@shopify/flash-list';
const listRef = useRef<FlashListRef<Article>>(null);
5. ודאו memoization יציב
כאן הרבה אנשים נתקעים. v2 עובדת קשה הרבה יותר כדי לוודא שפריטים לא עוברים re-render מיותר — אבל היא סומכת עליכם שה-props שלכם יהיו memoized כמו שצריך. חובה לעטוף את data ואת renderItem ב-useMemo/useCallback, ולספק keyExtractor יציב:
const data = useMemo(() => articles, [articles]);
const renderItem = useCallback(({ item }) => (
<ArticleCard item={item} />
), []);
const keyExtractor = useCallback((item: Article) => item.id, []);
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
Masonry: יצירת פריסות מורכבות
ה-prop masonry החדש פותח דרך ליצירת פריסות כמו של פינטרסט, גם עם גובה פריטים דינמי. בצירוף עם overrideItemLayout אפשר לקבוע שפריט מסוים יתפרש על שתי עמודות (למשל, מודעה או פריט מודגש):
<FlashList
data={photos}
numColumns={3}
masonry
optimizeItemArrangement
overrideItemLayout={(layout, item) => {
layout.span = item.isFeatured ? 2 : 1;
}}
renderItem={({ item }) => (
<Image source={{ uri: item.uri }} style={{ aspectRatio: item.ratio }} />
)}
keyExtractor={(item) => item.id}
/>
רשימות אופקיות — שיפור משמעותי
ב-v1, רשימות אופקיות היו כאב ראש: הגובה של הפריטים היה חייב להיות זהה, או שהחישובים היו נשברים לנו. ב-v2, רשימה אופקית מודיעה להורה שלה לחכות למדידת הגבהים לפני רינדור, כך שלא נוצרים over-draw או קפיצות.
<FlashList
horizontal
data={categories}
renderItem={({ item }) => (
<CategoryChip title={item.name} />
)}
keyExtractor={(item) => item.id}
/>
שילוב של רשימה אופקית בתוך רשימה אנכית (carousel ראשי בתוך feed, לדוגמה) עובד חלק בהרבה ב-v2 בזכות הקואורדינציה הזו. זה אחד השיפורים שהרגשתי מיד במעבר.
טכניקות אופטימיזציה מתקדמות
getItemType לרשימות הטרוגניות
אם הרשימה שלכם מכילה סוגי פריטים שונים (כותרת, מודעה, פוסט, banner), ספקו getItemType כדי ש-FlashList תמחזר רק פריטים מאותו סוג. התוצאה? אפס flickering ו-layout shifts:
<FlashList
data={feed}
getItemType={(item) => item.kind} // 'post' | 'ad' | 'header'
renderItem={({ item }) => {
switch (item.kind) {
case 'post': return <PostCard post={item} />;
case 'ad': return <AdSlot data={item} />;
case 'header': return <SectionHeader text={item.text} />;
}
}}
keyExtractor={(item) => item.id}
/>
onStartReached עם סף
חדש ב-v2: לא רק onEndReached אלא גם onStartReached עם threshold משלה — מושלם לטעינת הודעות ישנות בצ'אט (סוף סוף אפשר לזרוק את ה-hack הזה שכולנו כתבנו עם scroll events):
<FlashList
data={messages}
inverted
onStartReached={loadOlderMessages}
onStartReachedThreshold={0.2}
renderItem={renderMessage}
keyExtractor={(m) => m.id}
/>
Sticky headers חלקים
ה-sticky headers ב-v2 משתמשים ב-Animated implementation, כך שפערים קטנים בין ה-header לפריט הבא נעלמים לגמרי בזמן גלילה. פשוט ספקו stickyHeaderIndices:
<FlashList
data={itemsWithSectionHeaders}
stickyHeaderIndices={sectionHeaderIndices}
renderItem={renderRow}
keyExtractor={(item) => item.id}
/>
מתי להישאר עם FlatList
למרות היתרונות, יש מקרים שבהם FlatList עדיין עדיף:
- אתם עדיין על הארכיטקטורה הישנה ולא יכולים להעביר את הפרויקט ל-Fabric בטווח הנראה לעין.
- רשימה קטנה (פחות מ-50 פריטים) עם פריטים פשוטים — ההפרש בפועל זניח, ואולי עדיף לא להוסיף תלות חדשה בשביל זה.
- רשימה דינמית מאוד עם פריטים בגבהים בלתי צפויים וקיצוניים. במקרים האלה תצטרכו לכוון ידנית את FlatList עם
initialNumToRender={10},maxToRenderPerBatch={5},windowSize={21},removeClippedSubviewsבאנדרואיד, ו-getItemLayoutאם הגובה ידוע.
בעיות נפוצות ופתרונן
"FlashList's rendered size is not usable"
האזהרה הזו (מכירים אותה? היא מעצבנת) מופיעה כשה-FlashList נמצא בתוך container עם גובה 0 או עם flex לא מוגדר. הפתרון: ודאו שהאב מקבל flex: 1 או גובה מוגדר:
<View style={{ flex: 1 }}>
<FlashList ... />
</View>
פריטים קופצים או מתעדכנים באופן לא צפוי
זה כמעט תמיד נובע מכך ש-data או renderItem לא memoized. עטפו ב-useMemo/useCallback ועברו על ה-children עם React.memo כשזה הגיוני. 90% מהבאגים שנתקלתי בהם בגרסה הזו התגלו כבעיות memoization, אז זה המקום הראשון להסתכל בו.
גלילה מקרטעת עם תמונות
השתמשו ב-expo-image או ב-react-native-fast-image במקום ב-Image הסטנדרטי, והקפידו להגדיר width/height מפורשים בפריט. זה מונע מ-FlashList להמתין למדידה אחרי שהתמונה נטענה.
ה-App קורס בבניית release על אנדרואיד
ודאו שאתם על RN 0.76+ (או Expo SDK 52+) עם Hermes כברירת מחדל והארכיטקטורה החדשה מופעלת. FlashList v2 מסתמכת על יכולות מדידה של Fabric שפשוט לא קיימות ב-Paper.
שאלות נפוצות
האם חובה לעבור לארכיטקטורה החדשה כדי להשתמש ב-FlashList?
לגרסה v2 — כן, אין דרך לעקוף את זה. לגרסה v1.x עדיין יש תמיכה בארכיטקטורה הישנה, אבל היא לא תקבל תכונות חדשות. אם הפרויקט לא יכול לעבור עכשיו, הישארו על v1.x ותכננו את המעבר בקרוב.
האם FlashList v2 צריך estimatedItemSize?
לא, ובגדול. זה אחד השינויים המרכזיים: ב-v2 הספרייה מחשבת גדלים אוטומטית דרך מדידות סינכרוניות של Fabric. אם יש לכם את ה-prop הזה בקוד, תתעלמו מהצעקות של ה-TypeScript ותסירו אותו — הוא פשוט לא עושה כלום.
האם אפשר להחליף FlatList ב-FlashList ישירות?
ברוב המקרים כן: ה-API תואם מאוד. ההבדל העיקרי הוא הדרישה ל-keyExtractor יציב, ולפעמים צריך להוסיף getItemType לרשימות עם סוגי פריטים שונים. כדאי גם לעבור על ה-memoization של renderItem ו-data.
האם FlashList עובד עם React Native Web?
כן, ומאז v2 העבודה מול Web משופרת בטירוף. מכיוון שהספרייה היא JS-only, מרבית התכונות החדשות זמינות גם ב-Web. זה מקל במיוחד על פרויקטים multi-platform ב-Expo.
איך מודדים את השיפור בפועל אחרי המעבר?
השתמשו ב-Performance Monitor המובנה (Cmd+D / Cmd+M ← Perf Monitor) וצפו ב-JS FPS וב-UI FPS בזמן גלילה. בנוסף, Flipper או ה-Hermes Profiler יציגו לכם את ניצולת ה-CPU של ה-JS thread. אצל Shopify הירידה הייתה מ-90%+ ל-10%. ציפו לסדר גודל דומה בפרודקשן — אני אישית ראיתי ירידה מ-80% ל-12%, וזה עשה הבדל אמיתי בחוויית המשתמש.
סיכום
FlashList v2 הוא קפיצה משמעותית קדימה — לא רק בביצועים אלא גם בנוחות הפיתוח. הסרת estimatedItemSize, ה-masonry prop המובנה, הרשימות האופקיות הדינמיות ותמיכת ה-RTL הופכים אותה לספריית הרשימות ברירת המחדל של 2026 לכל אפליקציית React Native רצינית.
הדרישה היחידה האמיתית היא המעבר לארכיטקטורה החדשה — ואם אתם עדיין לא שם, זה הזמן הנכון לעשות את זה בכל מקרה. שילוב של FlashList v2, Hermes, Fabric ו-TurboModules נותן היום חוויית משתמש שקרובה מאוד לאפליקציות נייטיביות, עם מחיר הפיתוח של React Native. שווה לנסות — אתם לא תתחרטו.