react-native-sop-docs/07-performance-optimization/optimization.md

477 lines
11 KiB
Markdown

# Performance Optimization
## Hermes Setup
### Enable Hermes
**Android (android/app/build.gradle):**
```gradle
project.ext.react = [
enableHermes: true
]
```
**iOS (Podfile):**
```ruby
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => true
)
```
### Hermes Benefits
- Faster app startup time
- Reduced memory usage
- Smaller bundle size
- Better performance on lower-end devices
## List Optimization
### Optimized FlatList
```typescript
// src/components/OptimizedList/OptimizedList.tsx
import React, {useCallback, useMemo} from 'react';
import {FlatList, ListRenderItem, ViewToken} from 'react-native';
interface OptimizedListProps<T> {
data: T[];
renderItem: ListRenderItem<T>;
keyExtractor: (item: T, index: number) => string;
itemHeight?: number;
onEndReached?: () => void;
onViewableItemsChanged?: (info: {viewableItems: ViewToken[]}) => void;
}
export const OptimizedList = <T,>({
data,
renderItem,
keyExtractor,
itemHeight = 100,
onEndReached,
onViewableItemsChanged,
}: OptimizedListProps<T>) => {
const memoizedRenderItem = useCallback(renderItem, []);
const memoizedKeyExtractor = useCallback(keyExtractor, []);
const getItemLayout = useMemo(
() =>
itemHeight
? (data: any, index: number) => ({
length: itemHeight,
offset: itemHeight * index,
index,
})
: undefined,
[itemHeight]
);
const viewabilityConfig = useMemo(
() => ({
itemVisiblePercentThreshold: 50,
minimumViewTime: 300,
}),
[]
);
return (
<FlatList
data={data}
renderItem={memoizedRenderItem}
keyExtractor={memoizedKeyExtractor}
getItemLayout={getItemLayout}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={10}
updateCellsBatchingPeriod={50}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
/>
);
};
```
### Virtualized List for Large Datasets
```typescript
// src/components/VirtualizedList/VirtualizedList.tsx
import React from 'react';
import {VirtualizedList, ListRenderItem} from 'react-native';
interface VirtualizedListProps<T> {
data: T[];
renderItem: ListRenderItem<T>;
keyExtractor: (item: T, index: number) => string;
itemHeight: number;
}
export const CustomVirtualizedList = <T,>({
data,
renderItem,
keyExtractor,
itemHeight,
}: VirtualizedListProps<T>) => {
const getItem = (data: T[], index: number) => data[index];
const getItemCount = (data: T[]) => data.length;
return (
<VirtualizedList
data={data}
initialNumToRender={4}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemCount={getItemCount}
getItem={getItem}
getItemLayout={(data, index) => ({
length: itemHeight,
offset: itemHeight * index,
index,
})}
/>
);
};
```
## Image Optimization
### Lazy Loading Images
```typescript
// src/components/LazyImage/LazyImage.tsx
import React, {useState, useRef} from 'react';
import {View, Image, Animated, StyleSheet} from 'react-native';
import FastImage from 'react-native-fast-image';
interface LazyImageProps {
source: {uri: string};
style?: any;
placeholder?: React.ReactNode;
}
export const LazyImage: React.FC<LazyImageProps> = ({
source,
style,
placeholder,
}) => {
const [loaded, setLoaded] = useState(false);
const opacity = useRef(new Animated.Value(0)).current;
const onLoad = () => {
setLoaded(true);
Animated.timing(opacity, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
};
return (
<View style={style}>
{!loaded && placeholder}
<Animated.View style={[StyleSheet.absoluteFill, {opacity}]}>
<FastImage
source={source}
style={StyleSheet.absoluteFill}
onLoad={onLoad}
resizeMode={FastImage.resizeMode.cover}
/>
</Animated.View>
</View>
);
};
```
## Memory Management
### Component Memoization
```typescript
// src/components/MemoizedComponent/MemoizedComponent.tsx
import React, {memo, useMemo, useCallback} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
interface ItemProps {
id: string;
title: string;
description: string;
onPress: (id: string) => void;
}
const ItemComponent: React.FC<ItemProps> = memo(({
id,
title,
description,
onPress,
}) => {
const handlePress = useCallback(() => {
onPress(id);
}, [id, onPress]);
const formattedDescription = useMemo(() => {
return description.length > 100
? `${description.substring(0, 100)}...`
: description;
}, [description]);
return (
<TouchableOpacity onPress={handlePress}>
<View>
<Text>{title}</Text>
<Text>{formattedDescription}</Text>
</View>
</TouchableOpacity>
);
});
export default ItemComponent;
```
### Custom Hooks Optimization
```typescript
// src/hooks/useOptimizedData.ts
import {useMemo, useCallback} from 'react';
import {useAppSelector} from '@redux/hooks';
export const useOptimizedData = (filter?: string) => {
const rawData = useAppSelector(state => state.data.items);
const filteredData = useMemo(() => {
if (!filter) return rawData;
return rawData.filter(item =>
item.title.toLowerCase().includes(filter.toLowerCase())
);
}, [rawData, filter]);
const processItem = useCallback((item: any) => {
return {
...item,
displayTitle: item.title.toUpperCase(),
isNew: Date.now() - item.createdAt < 86400000, // 24 hours
};
}, []);
const processedData = useMemo(() =>
filteredData.map(processItem),
[filteredData, processItem]
);
return processedData;
};
```
## Bundle Optimization
### Code Splitting
```typescript
// src/utils/lazyImports.ts
import {lazy} from 'react';
// Lazy load heavy screens
export const ProfileScreen = lazy(() => import('@screens/Profile/ProfileScreen'));
export const SettingsScreen = lazy(() => import('@screens/Settings/SettingsScreen'));
export const ReportsScreen = lazy(() => import('@screens/Reports/ReportsScreen'));
// Lazy load heavy components
export const ChartComponent = lazy(() => import('@components/Chart/ChartComponent'));
export const MapComponent = lazy(() => import('@components/Map/MapComponent'));
```
### Dynamic Imports
```typescript
// src/utils/dynamicImports.ts
export const loadChartLibrary = async () => {
const {Chart} = await import('react-native-chart-kit');
return Chart;
};
export const loadMapLibrary = async () => {
const MapView = await import('react-native-maps');
return MapView.default;
};
// Usage in component
const MyComponent = () => {
const [ChartComponent, setChartComponent] = useState(null);
useEffect(() => {
loadChartLibrary().then(setChartComponent);
}, []);
return ChartComponent ? <ChartComponent /> : <LoadingSpinner />;
};
```
## Animation Optimization
### Native Driver Usage
```typescript
// src/animations/optimizedAnimations.ts
import {Animated, Easing} from 'react-native';
export const createOptimizedAnimation = (
animatedValue: Animated.Value,
toValue: number,
duration: number = 300
) => {
return Animated.timing(animatedValue, {
toValue,
duration,
easing: Easing.out(Easing.quad),
useNativeDriver: true, // Use native driver for better performance
});
};
export const createSpringAnimation = (
animatedValue: Animated.Value,
toValue: number
) => {
return Animated.spring(animatedValue, {
toValue,
tension: 100,
friction: 8,
useNativeDriver: true,
});
};
```
## Network Optimization
### Request Batching
```typescript
// src/utils/requestBatcher.ts
class RequestBatcher {
private queue: Array<{
url: string;
options: RequestInit;
resolve: (value: any) => void;
reject: (reason: any) => void;
}> = [];
private batchTimeout: NodeJS.Timeout | null = null;
public request(url: string, options: RequestInit = {}): Promise<any> {
return new Promise((resolve, reject) => {
this.queue.push({url, options, resolve, reject});
if (this.batchTimeout) {
clearTimeout(this.batchTimeout);
}
this.batchTimeout = setTimeout(() => {
this.processBatch();
}, 50); // Batch requests within 50ms
});
}
private async processBatch() {
const batch = [...this.queue];
this.queue = [];
this.batchTimeout = null;
// Group similar requests
const grouped = batch.reduce((acc, req) => {
const key = `${req.options.method || 'GET'}-${req.url.split('?')[0]}`;
if (!acc[key]) acc[key] = [];
acc[key].push(req);
return acc;
}, {} as Record<string, typeof batch>);
// Process each group
for (const requests of Object.values(grouped)) {
try {
const results = await Promise.all(
requests.map(req => fetch(req.url, req.options))
);
results.forEach((result, index) => {
requests[index].resolve(result);
});
} catch (error) {
requests.forEach(req => req.reject(error));
}
}
}
}
export const requestBatcher = new RequestBatcher();
```
## Performance Monitoring
### Performance Metrics
```typescript
// src/utils/performanceMonitor.ts
class PerformanceMonitor {
private metrics: Map<string, number> = new Map();
startTiming(label: string) {
this.metrics.set(label, Date.now());
}
endTiming(label: string): number {
const startTime = this.metrics.get(label);
if (!startTime) {
console.warn(`No start time found for ${label}`);
return 0;
}
const duration = Date.now() - startTime;
this.metrics.delete(label);
console.log(`${label}: ${duration}ms`);
return duration;
}
measureAsync<T>(label: string, fn: () => Promise<T>): Promise<T> {
this.startTiming(label);
return fn().finally(() => {
this.endTiming(label);
});
}
measureSync<T>(label: string, fn: () => T): T {
this.startTiming(label);
try {
return fn();
} finally {
this.endTiming(label);
}
}
}
export const performanceMonitor = new PerformanceMonitor();
// Usage
performanceMonitor.measureAsync('API_CALL', () =>
fetch('/api/data').then(res => res.json())
);
```
### Memory Usage Tracking
```typescript
// src/utils/memoryTracker.ts
export const trackMemoryUsage = () => {
if (__DEV__) {
const memoryInfo = (performance as any).memory;
if (memoryInfo) {
console.log('Memory Usage:', {
used: Math.round(memoryInfo.usedJSHeapSize / 1048576) + ' MB',
total: Math.round(memoryInfo.totalJSHeapSize / 1048576) + ' MB',
limit: Math.round(memoryInfo.jsHeapSizeLimit / 1048576) + ' MB',
});
}
}
};
// Track memory usage periodically
setInterval(trackMemoryUsage, 30000); // Every 30 seconds in development
```