Ders İçeriği

Performans Optimizasyonu Nedir?

React Native uygulamalarında performans optimizasyonu, uygulamanızın daha hızlı çalışması, daha az bellek kullanması ve daha iyi kullanıcı deneyimi sunması için yapılan iyileştirmelerdir. Bu derste en etkili optimizasyon tekniklerini öğreneceksiniz.

Performans Optimizasyonu Alanları:
  • Render Optimizasyonu: Gereksiz render'ları önleme
  • Bellek Yönetimi: Memory leak'leri önleme
  • Bundle Optimizasyonu: Uygulama boyutunu küçültme
  • Network Optimizasyonu: API isteklerini optimize etme
  • Image Optimizasyonu: Görsel performansını artırma
  • Navigation Optimizasyonu: Sayfa geçişlerini hızlandırma

Performans Metrikleri

FPS (Frames Per Second)60
Memory Usage45 MB
Bundle Size2.3 MB
Load Time1.2s
  

React.memo ve useMemo ile Render Optimizasyonu

Gereksiz render'ları önlemek performansın en önemli parçasıdır. React.memo ve useMemo hook'ları bu konuda yardımcı olur:

// React.memo ile bileşen optimizasyonu import React, { memo, useMemo, useCallback, useState } from 'react'; import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native'; // Optimize edilmemiş bileşen const UnoptimizedUserItem = ({ user, onPress }) => { console.log('UnoptimizedUserItem render:', user.name); return ( <TouchableOpacity style={styles.userItem} onPress={() => onPress(user.id)}> <Text style={styles.userName}>{user.name}</Text> <Text style={styles.userEmail}>{user.email}</Text> <Text style={styles.userScore}>Score: {user.score * 2}</Text> {/* Pahalı hesaplama */} </TouchableOpacity> ); }; // React.memo ile optimize edilmiş bileşen const OptimizedUserItem = memo(({ user, onPress }) => { console.log('OptimizedUserItem render:', user.name); // useMemo ile pahalı hesaplamaları cache'le const calculatedScore = useMemo(() => { console.log('Score hesaplanıyor:', user.name); return user.score * 2; // Pahalı hesaplama simülasyonu }, [user.score]); return ( <TouchableOpacity style={styles.userItem} onPress={() => onPress(user.id)}> <Text style={styles.userName}>{user.name}</Text> <Text style={styles.userEmail}>{user.email}</Text> <Text style={styles.userScore}>Score: {calculatedScore}</Text> </TouchableOpacity> ); }, (prevProps, nextProps) => { // Custom karşılaştırma fonksiyonu return ( prevProps.user.id === nextProps.user.id && prevProps.user.name === nextProps.user.name && prevProps.user.email === nextProps.user.email && prevProps.user.score === nextProps.user.score ); }); // Ana bileşen const UserListApp = () => { const [users, setUsers] = useState([ { id: 1, name: 'John Doe', email: 'john@example.com', score: 85 }, { id: 2, name: 'Jane Smith', email: 'jane@example.com', score: 92 }, { id: 3, name: 'Bob Johnson', email: 'bob@example.com', score: 78 }, ]); const [selectedUserId, setSelectedUserId] = useState(null); // useCallback ile fonksiyonu cache'le const handleUserPress = useCallback((userId) => { setSelectedUserId(userId); }, []); // useMemo ile filtrelenmiş listeyi cache'le const filteredUsers = useMemo(() => { return users.filter(user => user.score > 80); }, [users]); // Kullanıcı skorunu güncelleme (test için) const updateUserScore = useCallback((userId) => { setUsers(prevUsers => prevUsers.map(user => user.id === userId ? { ...user, score: Math.floor(Math.random() * 100) } : user ) ); }, []); return ( <View style={styles.container}> <Text style={styles.title}>Kullanıcı Listesi (Optimize Edilmiş)</Text> <FlatList data={filteredUsers} keyExtractor={(item) => item.id.toString()} renderItem={({ item }) => ( <OptimizedUserItem user={item} onPress={handleUserPress} /> )} // FlatList optimizasyonları removeClippedSubviews={true} maxToRenderPerBatch={10} updateCellsBatchingPeriod={50} initialNumToRender={10} windowSize={10} getItemLayout={(data, index) => ({ length: 80, offset: 80 * index, index, })} /> {selectedUserId && ( <View style={styles.selectedUser}> <Text>Seçili Kullanıcı ID: {selectedUserId}</Text> <TouchableOpacity style={styles.updateButton} onPress={() => updateUserScore(selectedUserId)} > <Text style={styles.updateButtonText}>Skoru Güncelle</Text> </TouchableOpacity> </View> )} </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 20, backgroundColor: '#f8f9fa', }, title: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', marginBottom: 20, color: '#2c3e50', }, userItem: { backgroundColor: 'white', padding: 15, borderRadius: 8, marginBottom: 10, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 3.84, elevation: 5, }, userName: { fontSize: 18, fontWeight: 'bold', color: '#2c3e50', }, userEmail: { fontSize: 14, color: '#7f8c8d', marginTop: 5, }, userScore: { fontSize: 16, color: '#3498db', marginTop: 5, fontWeight: '600', }, selectedUser: { backgroundColor: '#e8f4fd', padding: 15, borderRadius: 8, marginTop: 20, alignItems: 'center', }, updateButton: { backgroundColor: '#3498db', padding: 10, borderRadius: 5, marginTop: 10, }, updateButtonText: { color: 'white', fontWeight: 'bold', }, }); export default UserListApp;

FlatList Optimizasyonu

Büyük listeler için FlatList optimizasyonu kritik öneme sahiptir:

// Optimize edilmiş FlatList import React, { memo, useCallback } from 'react'; import { FlatList, View, Text, Image, StyleSheet } from 'react-native'; // Liste öğesi bileşeni const ListItem = memo(({ item, index }) => { return ( <View style={styles.listItem}> <Image source={{ uri: item.avatar }} style={styles.avatar} // Image optimizasyonları resizeMode="cover" defaultSource={require('./placeholder.png')} /> <View style={styles.itemContent}> <Text style={styles.itemTitle}>{item.title}</Text> <Text style={styles.itemSubtitle}>{item.subtitle}</Text> </View> </View> ); }); const OptimizedFlatList = ({ data }) => { // Öğe boyutunu hesaplama (getItemLayout için) const ITEM_HEIGHT = 80; const getItemLayout = useCallback((data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index, }), []); // Anahtar çıkarma fonksiyonu const keyExtractor = useCallback((item) => item.id.toString(), []); // Render fonksiyonu const renderItem = useCallback(({ item, index }) => ( <ListItem item={item} index={index} /> ), []); return ( <FlatList data={data} renderItem={renderItem} keyExtractor={keyExtractor} getItemLayout={getItemLayout} // Performans optimizasyonları removeClippedSubviews={true} // Görünmeyen öğeleri DOM'dan kaldır maxToRenderPerBatch={10} // Batch başına maksimum render sayısı updateCellsBatchingPeriod={50} // Batch güncelleme periyodu (ms) initialNumToRender={10} // İlk render'da gösterilecek öğe sayısı windowSize={10} // Viewport çevresinde tutulacak öğe sayısı // Scroll optimizasyonları scrollEventThrottle={16} // Scroll event throttling (60 FPS için) disableVirtualization={false} // Virtualization'ı etkinleştir // Memory optimizasyonları onEndReachedThreshold={0.5} // Listenin sonuna yaklaşma eşiği legacyImplementation={false} // Yeni implementasyonu kullan // Görsel optimizasyonlar showsVerticalScrollIndicator={false} // Scroll bar'ı gizle overScrollMode="never" // Android over-scroll efektini kapat // Boş liste durumu ListEmptyComponent={() => ( <View style={styles.emptyContainer}> <Text style={styles.emptyText}>Henüz veri yok</Text> </View> )} // Liste ayırıcısı ItemSeparatorComponent={() => <View style={styles.separator} />} /> ); }; const styles = StyleSheet.create({ listItem: { flexDirection: 'row', padding: 15, backgroundColor: 'white', alignItems: 'center', }, avatar: { width: 50, height: 50, borderRadius: 25, marginRight: 15, }, itemContent: { flex: 1, }, itemTitle: { fontSize: 16, fontWeight: 'bold', color: '#2c3e50', }, itemSubtitle: { fontSize: 14, color: '#7f8c8d', marginTop: 5, }, separator: { height: 1, backgroundColor: '#ecf0f1', }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 50, }, emptyText: { fontSize: 16, color: '#7f8c8d', }, }); export default OptimizedFlatList;

Image Optimizasyonu

Görseller uygulamanızın performansını büyük ölçüde etkileyebilir. Doğru optimizasyon teknikleri kullanmak önemlidir:

// Image optimizasyonu import React, { useState } from 'react'; import { View, Image, ActivityIndicator, StyleSheet } from 'react-native'; import FastImage from 'react-native-fast-image'; // Daha hızlı image loading // Optimize edilmiş Image bileşeni const OptimizedImage = ({ source, style, placeholder, resizeMode = 'cover', priority = 'normal', cache = true }) => { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); return ( <View style={[styles.imageContainer, style]}> {/* FastImage kullanımı (daha iyi performans) */} <FastImage style={StyleSheet.absoluteFillObject} source={{ uri: source, priority: FastImage.priority[priority], cache: cache ? FastImage.cacheControl.immutable : FastImage.cacheControl.web, }} resizeMode={FastImage.resizeMode[resizeMode]} onLoadStart={() => setLoading(true)} onLoad={() => setLoading(false)} onError={() => { setLoading(false); setError(true); }} fallback={error} // Android için fallback /> {/* Loading göstergesi */} {loading && ( <View style={styles.loadingContainer}> <ActivityIndicator size="small" color="#3498db" /> </View> )} {/* Placeholder image */} {error && placeholder && ( <Image source={placeholder} style={StyleSheet.absoluteFillObject} resizeMode={resizeMode} /> )} </View> ); }; // Image cache yönetimi const ImageCacheManager = { // Cache'i temizle clearCache: async () => { try { await FastImage.clearMemoryCache(); await FastImage.clearDiskCache(); console.log('Image cache temizlendi'); } catch (error) { console.error('Cache temizleme hatası:', error); } }, // Image'i önceden yükle preloadImages: async (imageUrls) => { try { const preloadTasks = imageUrls.map(url => ({ uri: url, priority: FastImage.priority.high, })); await FastImage.preload(preloadTasks); console.log('Images preloaded'); } catch (error) { console.error('Preload hatası:', error); } }, // Cache boyutunu kontrol et getCacheSize: async () => { try { // Bu fonksiyon custom implementation gerektirir // Native module ile cache boyutunu alabilirsiniz return '0 MB'; } catch (error) { console.error('Cache boyutu alınamadı:', error); return 'Bilinmiyor'; } }, }; // Responsive image boyutları const getOptimalImageSize = (screenWidth, imageAspectRatio) => { const maxWidth = screenWidth * 0.9; const maxHeight = 300; let width = maxWidth; let height = width / imageAspectRatio; if (height > maxHeight) { height = maxHeight; width = height * imageAspectRatio; } return { width, height }; }; // Image lazy loading const LazyImage = ({ source, style, ...props }) => { const [shouldLoad, setShouldLoad] = useState(false); const onViewableItemsChanged = useCallback(({ viewableItems }) => { // Görünür hale geldiğinde yükle setShouldLoad(true); }, []); return ( <View style={style}> {shouldLoad ? ( <OptimizedImage source={source} style={style} {...props} /> ) : ( <View style={[style, styles.placeholder]} /> )} </View> ); }; // WebP format desteği const WebPImage = ({ source, ...props }) => { const webpSource = source.replace(/\.(jpg|jpeg|png)$/i, '.webp'); return ( <OptimizedImage source={webpSource} {...props} onError={() => { // WebP desteklenmiyorsa orijinal format'a geri dön return <OptimizedImage source={source} {...props} />; }} /> ); }; const styles = StyleSheet.create({ imageContainer: { backgroundColor: '#f0f0f0', justifyContent: 'center', alignItems: 'center', }, loadingContainer: { ...StyleSheet.absoluteFillObject, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(255, 255, 255, 0.8)', }, placeholder: { backgroundColor: '#ecf0f1', justifyContent: 'center', alignItems: 'center', }, }); export { OptimizedImage, ImageCacheManager, LazyImage, WebPImage };

Bundle Optimizasyonu

Uygulama boyutunu küçültmek ve yükleme süresini azaltmak için bundle optimizasyonu yapabilirsiniz:

// Metro bundler konfigürasyonu (metro.config.js) const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); // Bundle optimizasyonları config.transformer = { ...config.transformer, minifierConfig: { // Terser minifier ayarları mangle: { keep_fnames: true, // Fonksiyon isimlerini koru (debugging için) }, compress: { drop_console: true, // Console.log'ları kaldır (production'da) }, }, }; // Tree shaking için config.resolver = { ...config.resolver, alias: { // Lodash'ın sadece kullanılan kısımlarını import et 'lodash': 'lodash-es', }, }; module.exports = config; // Package.json scripts optimizasyonu { "scripts": { "build:android": "expo build:android --clear-cache", "build:ios": "expo build:ios --clear-cache", "analyze-bundle": "npx react-native-bundle-visualizer", "optimize-images": "imageoptim --directory ./assets/images" } } // Lazy loading ile kod bölme import React, { lazy, Suspense } from 'react'; import { ActivityIndicator, View } from 'react-native'; // Lazy loaded bileşenler const ProfileScreen = lazy(() => import('./screens/ProfileScreen')); const SettingsScreen = lazy(() => import('./screens/SettingsScreen')); const ChatScreen = lazy(() => import('./screens/ChatScreen')); // Loading bileşeni const LoadingScreen = () => ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <ActivityIndicator size="large" color="#3498db" /> </View> ); // Ana navigasyon const AppNavigator = () => { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Profile" component={() => ( <Suspense fallback={<LoadingScreen />}> <ProfileScreen /> </Suspense> )} /> <Stack.Screen name="Settings" component={() => ( <Suspense fallback={<LoadingScreen />}> <SettingsScreen /> </Suspense> )} /> </Stack.Navigator> </NavigationContainer> ); }; // Dinamik import'lar const loadFeature = async (featureName) => { try { switch (featureName) { case 'camera': const { CameraModule } = await import('./features/camera'); return CameraModule; case 'maps': const { MapsModule } = await import('./features/maps'); return MapsModule; case 'analytics': const { AnalyticsModule } = await import('./features/analytics'); return AnalyticsModule; default: throw new Error(`Feature ${featureName} not found`); } } catch (error) { console.error(`Feature loading error: ${featureName}`, error); return null; } }; // Conditional imports (platform specific) const PlatformSpecificComponent = () => { const [Component, setComponent] = useState(null); useEffect(() => { const loadPlatformComponent = async () => { if (Platform.OS === 'ios') { const { IOSComponent } = await import('./components/IOSComponent'); setComponent(() => IOSComponent); } else { const { AndroidComponent } = await import('./components/AndroidComponent'); setComponent(() => AndroidComponent); } }; loadPlatformComponent(); }, []); return Component ? <Component /> : <LoadingScreen />; }; // Bundle analyzer kullanımı // Terminal'de çalıştır: // npx react-native-bundle-visualizer // Bu komut bundle'ınızın hangi kısımlarının ne kadar yer kapladığını gösterir

📊 Optimizasyon Teknikleri Karşılaştırması

TeknikPerformans EtkisiUygulama ZorluğuKullanım Alanı
React.memoYüksekKolayBileşen re-render'ları
useMemoYüksekKolayPahalı hesaplamalar
useCallbackOrtaKolayFonksiyon referansları
FlatList OptimizasyonuYüksekOrtaBüyük listeler
Image OptimizasyonuYüksekOrtaGörsel yoğun uygulamalar
Bundle SplittingOrtaZorBüyük uygulamalar
Lazy LoadingOrtaOrtaSayfa geçişleri

Memory Management

Bellek sızıntılarını önlemek ve uygulamanızın stabil çalışmasını sağlamak için memory management önemlidir:

// Memory leak önleme teknikleri import React, { useEffect, useRef, useState } from 'react'; // 1. Event listener'ları temizleme const EventListenerComponent = () => { useEffect(() => { const handleScroll = () => { console.log('Scrolling...'); }; // Event listener ekle window.addEventListener('scroll', handleScroll); // Cleanup function - component unmount olduğunda çalışır return () => { window.removeEventListener('scroll', handleScroll); }; }, []); return <View />; }; // 2. Timer'ları temizleme const TimerComponent = () => { const [count, setCount] = useState(0); const intervalRef = useRef(null); const timeoutRef = useRef(null); useEffect(() => { // Interval başlat intervalRef.current = setInterval(() => { setCount(prev => prev + 1); }, 1000); // Timeout ayarla timeoutRef.current = setTimeout(() => { console.log('Timeout completed'); }, 5000); // Cleanup return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); return <Text>Count: {count}</Text>; }; // 3. Subscription'ları temizleme const SubscriptionComponent = () => { const [data, setData] = useState(null); const subscriptionRef = useRef(null); useEffect(() => { // API subscription subscriptionRef.current = apiService.subscribe((newData) => { setData(newData); }); // Cleanup return () => { if (subscriptionRef.current) { subscriptionRef.current.unsubscribe(); } }; }, []); return <Text>{data}</Text>; }; // 4. Async işlemleri iptal etme const AsyncComponent = () => { const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const isMountedRef = useRef(true); useEffect(() => { const fetchData = async () => { setLoading(true); try { const response = await fetch('/api/data'); const result = await response.json(); // Component hala mount edilmiş mi kontrol et if (isMountedRef.current) { setData(result); setLoading(false); } } catch (error) { if (isMountedRef.current) { console.error('Fetch error:', error); setLoading(false); } } }; fetchData(); // Cleanup return () => { isMountedRef.current = false; }; }, []); if (loading) return <Text>Loading...</Text>; return <Text>{data}</Text>; }; // 5. AbortController ile fetch iptal etme const AbortableComponent = () => { const [data, setData] = useState(null); const abortControllerRef = useRef(null); useEffect(() => { const fetchData = async () => { // Yeni AbortController oluştur abortControllerRef.current = new AbortController(); try { const response = await fetch('/api/data', { signal: abortControllerRef.current.signal }); const result = await response.json(); setData(result); } catch (error) { if (error.name !== 'AbortError') { console.error('Fetch error:', error); } } }; fetchData(); // Cleanup - fetch'i iptal et return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, []); return <Text>{data}</Text>; }; // 6. Memory monitoring const MemoryMonitor = () => { const [memoryInfo, setMemoryInfo] = useState({}); useEffect(() => { const checkMemory = () => { if (performance.memory) { setMemoryInfo({ used: Math.round(performance.memory.usedJSHeapSize / 1048576), // MB total: Math.round(performance.memory.totalJSHeapSize / 1048576), // MB limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576), // MB }); } }; const interval = setInterval(checkMemory, 5000); checkMemory(); // İlk çalıştırma return () => clearInterval(interval); }, []); return ( <View style={{ padding: 10, backgroundColor: '#f0f0f0' }}> <Text>Memory Usage: {memoryInfo.used} MB</Text> <Text>Total Heap: {memoryInfo.total} MB</Text> <Text>Heap Limit: {memoryInfo.limit} MB</Text> </View> ); }; // 7. WeakMap ve WeakSet kullanımı class CacheManager { constructor() { // WeakMap garbage collection'a izin verir this.cache = new WeakMap(); this.listeners = new WeakSet(); } setCache(object, data) { this.cache.set(object, data); } getCache(object) { return this.cache.get(object); } addListener(listener) { this.listeners.add(listener); } hasListener(listener) { return this.listeners.has(listener); } } export { EventListenerComponent, TimerComponent, SubscriptionComponent, AsyncComponent, AbortableComponent, MemoryMonitor, CacheManager };

Memory Usage Simülasyonu

45% (45 MB / 100 MB)
Render Time:
16ms
JS Thread:
12ms
UI Thread:
8ms
  

Sonuç

Bu derste React Native uygulamalarında performans optimizasyonu tekniklerini öğrendiniz. Bu teknikler sayesinde uygulamanız daha hızlı çalışacak, daha az bellek kullanacak ve kullanıcılarınıza daha iyi bir deneyim sunacaksınız.

🎉 Tebrikler! React Native eğitim serisini tamamladınız! Artık profesyonel mobil uygulamalar geliştirebilecek bilgi ve beceriye sahipsiniz.
Bu Derste Öğrendikleriniz:
  • React.memo ve useMemo ile render optimizasyonu
  • FlatList performans iyileştirmeleri
  • Image optimizasyonu teknikleri
  • Bundle boyutu küçültme yöntemleri
  • Memory leak önleme stratejileri
  • Performans monitoring araçları