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ı önlemeBellek Yönetimi: Memory leak'leri önlemeBundle Optimizasyonu: Uygulama boyutunu küçültmeNetwork Optimizasyonu: API isteklerini optimize etmeImage Optimizasyonu: Görsel performansını artırmaNavigation Optimizasyonu: Sayfa geçişlerini hızlandırmaReact.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:
Kopyala // 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:
Kopyala // 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:
Kopyala // 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:
Kopyala // 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ı Teknik Performans Etkisi Uygulama Zorluğu Kullanım Alanı React.memo Yüksek Kolay Bileşen re-render'ları useMemo Yüksek Kolay Pahalı hesaplamalar useCallback Orta Kolay Fonksiyon referansları FlatList Optimizasyonu Yüksek Orta Büyük listeler Image Optimizasyonu Yüksek Orta Görsel yoğun uygulamalar Bundle Splitting Orta Zor Büyük uygulamalar Lazy Loading Orta Orta Sayfa 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:
Kopyala // 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 };
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ı