477 lines
11 KiB
Markdown
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
|
|
``` |