60fps או מוות, למה Performance ב-React Native שונה מה-Web
זה קרה באפליקציה אחת ב-production עם מיליון משתמשים פעילים: מסך מסוים גרם ל-battery drain של 15% בחמש דקות. חנות האפליקציות הציגה ביקורות: "האפליקציה מחממת את הטלפון". הדירוג ירד ל-2.8. הסיבה? FlatList עם תמונות שלא הוטמעו כראוי, animations שרצו על ה-JS thread, ו-useEffect עם dependency array שגוי שגרם ל-loop אינסופי של API calls.
Performance ב-React Native שונה מהווב. יש שני threads: ה-JS thread (לוגיקה, state, event handlers) וה-UI thread (native rendering). כשה-JS thread עמוס, animations מתגמגמות. הדרך לתקן: להעביר כמה שיותר עבודה ל-UI thread, ולהפחית render cycles מיותרים. Claude עוזר לזהות, לנתח, ולתקן את שתי הבעיות במהירות.
עדכון 2025: מאז React Native 0.76, New Architecture מופעלת כברירת מחדל, Fabric renderer, JSI (JavaScript Interface), ו-TurboModules. Fabric מעניק concurrent rendering ו-synchronous JS-native communication. Hermes 1.0 משפר startup ב-40% לעומת JSC. לפני כל micro-optimization, ודאו שה-New Architecture מופעלת.
Prompt ראשון: Audit Performance של Component
הפרומפט הזה שולח קוד ומקבל ניתוח מלא של בעיות performance:
You are a React Native performance expert.
// [1] domain ספציפי, performance, לא general RN
Audit this component for performance issues.
Identify: unnecessary re-renders, expensive computations,
memory leaks, missing memoization, and JS thread blockers.
// [2] checklist מפורש, Claude יכסה כל נושא בנפרד
[PASTE YOUR COMPONENT CODE HERE]
For each issue found:
1. Explain why it causes performance degradation
2. Show the fixed code
3. Estimate impact: low / medium / high
// [3] output מובנה, שלושה חלקים לכל issue
Also identify what to measure with Flipper or
React DevTools Profiler to confirm the fix.
// [4] measurement, performance ללא מדידה היא guessingFlashList v2, הכלי שמחליף FlatList ב-2025
FlatList מצוינת, אבל FlashList מ-Shopify משיגה ביצועים טובים משמעותית ברשימות ארוכות. במקום למחוק ולייצר components מחדש (virtualization), FlashList ממחזרת components, cell recycling. ב-2025 יצאה FlashList v2, rewrite מלא עבור New Architecture.
| מדד | FlatList (לפני) | FlashList v2 (אחרי) |
|---|---|---|
| ממוצע FPS | 36.9 | 56.9 (+54%) |
| CPU usage | 198.9% | 36.5% (-82%) |
| JS thread | מעל 90% | מתחת ל-10% |
| Blank area בגלילה | בסיסי | 50% פחות (vs v1) |
מ-Shopify Engineering: "FlashList v2 delivers faster load times, improved scrolling performance, and precise rendering without requiring item size estimates."
Migration מ-FlatList ל-FlashList v2 דורש שלושה שינויים עיקריים: החלפת import, החלפת שם הרכיב, והוספת estimatedItemSize. אם הרשימה שלכם מעל 50 items, שווה את ה-migration.
FlatList עם כל ה-Optimizations (כשנשארים)
כשFlashList לא מתאים (למשל, ביישום קיים עם New Architecture מורכבת), הנה FlatList מותאמת מלאה:
import React, { memo, useCallback, useMemo } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
const ITEM_HEIGHT = 88;
// React.memo עם custom comparison, re-render רק אם id או price השתנה
const ProductItem = memo(({ item, onPress }) => (
<View style={styles.item}>
<Text numberOfLines={2}>{item.name}</Text>
<Text>{item.price} ₪</Text>
</View>
), (prev, next) => prev.item.id === next.item.id && prev.item.price === next.item.price);
export const ProductList = ({ products, onProductPress }) => {
// useCallback: מונע יצירת function חדשה בכל render
const renderItem = useCallback(({ item }) => (
<ProductItem item={item} onPress={() => onProductPress(item.id)} />
), [onProductPress]);
// getItemLayout: FlatList יודע מיקום כל item מראש
const getItemLayout = useCallback((_, index) => ({
length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index,
}), []);
const keyExtractor = useCallback((item) => item.id, []);
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5} // default 21, גורם לconsume זיכרון מיותר
updateCellsBatchingPeriod={100}
removeClippedSubviews={true} // Android only
/>
);
};Reanimated, Animations על UI Thread תמיד
// טעות נפוצה: animation על JS thread, מתגמגמת כשJS עמוס
import { Animated } from 'react-native';
const opacity = new Animated.Value(0);
Animated.timing(opacity, { toValue: 1, duration: 300,
useNativeDriver: false // ❌ מריץ על JS thread
}).start();
// נכון: Reanimated 3, worklet על UI thread, 60fps תמיד
import Animated, { useSharedValue, useAnimatedStyle, withTiming }
from 'react-native-reanimated';
const opacity = useSharedValue(0);
const animStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
// withTiming רץ על UI thread כ-worklet, לא נחסם ע"י JS thread כבד
const fadeIn = () => { opacity.value = withTiming(1, { duration: 300 }); };טעויות נפוצות שClaude עוזר לזהות
- console.log ב-production hot path: כל
console.logב-renderItem או scroll handler עובר דרך JSI ומאט את JS thread. הוציאו עםbabel-plugin-transform-remove-consoleב-build של production. - Arrow function ישירה ב-renderItem:
renderItem={({ item }) => <MyComponent item={item} />}יוצר function object חדש בכל render של ה-parent. עטפו ב-useCallbackאו הוציאו לחוץ לחלוטין מהcomponent. - FlashList v1 עם New Architecture: v1 ביצע גרוע יותר על New Architecture לעומת legacy. אם אתם כבר על RN 0.76+, עברו ל-v2 בלבד.
- StyleSheet.create חסר:
style={{ marginTop: 8 }}inline יוצר object חדש בכל render.StyleSheet.createשולח IDs פעם אחת ל-native.
כלי מדידה, מה להפעיל מהיום הראשון
- React DevTools Profiler: ציר הזמן של re-renders, מזהה אילו components מתרנדרים ללא צורך
- Flipper + Hermes Profiler: ניתוח JS thread load frame-by-frame בזמן scroll
- Sentry Performance: custom transactions ב-production:
Sentry.startTransaction({ name: 'ProductList.render' }) - PerformanceObserver: API חדש ב-React Native 0.74+ למדידת JS frame rate בזמן scroll ב-production
הגדירו את כלי המדידה מהיום הראשון. כשתגיעו ל-battery drain ב-production, תוכלו לזהות את הבעיה תוך דקות, לא ימים.
