第8章:性能优化
📖 本章概述
性能优化是移动应用开发的重要环节。本章将介绍 React Native 应用的性能监控、分析和优化技巧,帮助你构建流畅、高效的移动应用。
📊 性能监控工具
React Native Performance Monitor
React Native 内置了性能监控工具,可以实时查看应用性能。
typescript
// 在开发模式下启用性能监控
// 摇晃设备 -> 选择 "Perf Monitor"
// 或在代码中启用
import {NativeModules} from 'react-native';
// 显示性能监控器
NativeModules.DevSettings.setIsPerfMonitorShown(true);
Flipper 性能分析
Flipper 提供了强大的性能分析功能:
- React DevTools - 组件层次结构和状态检查
- Network Inspector - 网络请求监控
- Layout Inspector - 布局检查和调试
- Crash Reporter - 崩溃报告
自定义性能监控
typescript
// src/utils/performanceMonitor.ts
export class PerformanceMonitor {
private static timers: Map<string, number> = new Map();
private static metrics: Map<string, number[]> = new Map();
// 开始计时
static startTimer(name: string): void {
this.timers.set(name, Date.now());
}
// 结束计时并记录
static endTimer(name: string): number {
const startTime = this.timers.get(name);
if (!startTime) {
console.warn(`Timer ${name} not found`);
return 0;
}
const duration = Date.now() - startTime;
this.timers.delete(name);
// 记录到指标中
const existing = this.metrics.get(name) || [];
existing.push(duration);
this.metrics.set(name, existing);
console.log(`⏱️ ${name}: ${duration}ms`);
return duration;
}
// 获取平均性能
static getAverageTime(name: string): number {
const times = this.metrics.get(name);
if (!times || times.length === 0) return 0;
const sum = times.reduce((a, b) => a + b, 0);
return sum / times.length;
}
// 获取所有指标
static getAllMetrics(): Record<string, {average: number; count: number}> {
const result: Record<string, {average: number; count: number}> = {};
this.metrics.forEach((times, name) => {
const sum = times.reduce((a, b) => a + b, 0);
result[name] = {
average: sum / times.length,
count: times.length,
};
});
return result;
}
// 清除所有指标
static clearMetrics(): void {
this.timers.clear();
this.metrics.clear();
}
}
// 装饰器用法
export function measurePerformance(name: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
PerformanceMonitor.startTimer(name);
try {
const result = await originalMethod.apply(this, args);
return result;
} finally {
PerformanceMonitor.endTimer(name);
}
};
return descriptor;
};
}
🚀 渲染性能优化
React.memo 优化组件重渲染
typescript
// src/components/OptimizedListItem.tsx
import React from 'react';
import {View, Text, TouchableOpacity, StyleSheet} from 'react-native';
interface ListItemProps {
id: number;
title: string;
description: string;
isSelected: boolean;
onPress: (id: number) => void;
}
// 使用 React.memo 避免不必要的重渲染
const OptimizedListItem = React.memo<ListItemProps>(({
id,
title,
description,
isSelected,
onPress,
}) => {
console.log(`渲染 ListItem ${id}`);
const handlePress = React.useCallback(() => {
onPress(id);
}, [id, onPress]);
return (
<TouchableOpacity
style={[styles.container, isSelected && styles.selected]}
onPress={handlePress}
>
<Text style={styles.title}>{title}</Text>
<Text style={styles.description}>{description}</Text>
</TouchableOpacity>
);
});
// 自定义比较函数
const OptimizedListItemWithCustomComparison = React.memo<ListItemProps>(
({id, title, description, isSelected, onPress}) => {
// 组件实现...
return null;
},
(prevProps, nextProps) => {
// 自定义比较逻辑
return (
prevProps.id === nextProps.id &&
prevProps.title === nextProps.title &&
prevProps.description === nextProps.description &&
prevProps.isSelected === nextProps.isSelected
);
}
);
const styles = StyleSheet.create({
container: {
padding: 15,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
selected: {
backgroundColor: '#e3f2fd',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
description: {
fontSize: 14,
color: '#666',
},
});
export default OptimizedListItem;
useCallback 和 useMemo 优化
typescript
// src/components/OptimizedList.tsx
import React, {useState, useCallback, useMemo} from 'react';
import {FlatList, View, TextInput, StyleSheet} from 'react-native';
import OptimizedListItem from './OptimizedListItem';
interface ListItem {
id: number;
title: string;
description: string;
}
const OptimizedList: React.FC = () => {
const [items, setItems] = useState<ListItem[]>([]);
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
const [searchText, setSearchText] = useState('');
// 使用 useCallback 缓存函数
const handleItemPress = useCallback((id: number) => {
setSelectedIds(prev => {
const newSet = new Set(prev);
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
return newSet;
});
}, []);
// 使用 useMemo 缓存计算结果
const filteredItems = useMemo(() => {
if (!searchText) return items;
return items.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()) ||
item.description.toLowerCase().includes(searchText.toLowerCase())
);
}, [items, searchText]);
// 使用 useCallback 缓存渲染函数
const renderItem = useCallback(({item}: {item: ListItem}) => (
<OptimizedListItem
id={item.id}
title={item.title}
description={item.description}
isSelected={selectedIds.has(item.id)}
onPress={handleItemPress}
/>
), [selectedIds, handleItemPress]);
// 使用 useCallback 缓存 keyExtractor
const keyExtractor = useCallback((item: ListItem) => item.id.toString(), []);
return (
<View style={styles.container}>
<TextInput
style={styles.searchInput}
value={searchText}
onChangeText={setSearchText}
placeholder="搜索..."
/>
<FlatList
data={filteredItems}
renderItem={renderItem}
keyExtractor={keyExtractor}
// 性能优化配置
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={10}
getItemLayout={(data, index) => ({
length: 70, // 假设每个项目高度为70
offset: 70 * index,
index,
})}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
searchInput: {
height: 40,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
margin: 10,
},
});
export default OptimizedList;
📱 内存管理
内存泄漏检测和预防
typescript
// src/hooks/useMemoryOptimization.ts
import {useEffect, useRef} from 'react';
export function useMemoryOptimization() {
const timersRef = useRef<NodeJS.Timeout[]>([]);
const listenersRef = useRef<(() => void)[]>([]);
// 添加定时器
const addTimer = (timer: NodeJS.Timeout) => {
timersRef.current.push(timer);
};
// 添加监听器清理函数
const addListener = (cleanup: () => void) => {
listenersRef.current.push(cleanup);
};
// 组件卸载时清理所有资源
useEffect(() => {
return () => {
// 清理定时器
timersRef.current.forEach(timer => clearTimeout(timer));
timersRef.current = [];
// 清理监听器
listenersRef.current.forEach(cleanup => cleanup());
listenersRef.current = [];
};
}, []);
return {addTimer, addListener};
}
// 使用示例
const ExampleComponent: React.FC = () => {
const {addTimer, addListener} = useMemoryOptimization();
useEffect(() => {
// 添加定时器
const timer = setTimeout(() => {
console.log('定时器执行');
}, 5000);
addTimer(timer);
// 添加事件监听器
const subscription = someEventEmitter.addListener('event', handler);
addListener(() => subscription.remove());
}, [addTimer, addListener]);
return null;
};
图片内存优化
typescript
// src/components/OptimizedImage.tsx
import React, {useState} from 'react';
import {Image, View, StyleSheet, Dimensions} from 'react-native';
const {width: screenWidth} = Dimensions.get('window');
interface OptimizedImageProps {
uri: string;
width?: number;
height?: number;
resizeMode?: 'cover' | 'contain' | 'stretch' | 'repeat' | 'center';
}
const OptimizedImage: React.FC<OptimizedImageProps> = ({
uri,
width = screenWidth,
height = 200,
resizeMode = 'cover',
}) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
return (
<View style={[styles.container, {width, height}]}>
<Image
source={{uri}}
style={[styles.image, {width, height}]}
resizeMode={resizeMode}
onLoad={() => setLoading(false)}
onError={() => {
setLoading(false);
setError(true);
}}
// 内存优化配置
cache="force-cache"
// 对于大图片,可以设置较小的缓存策略
{...(width > 500 && {cache: 'reload'})}
/>
{loading && (
<View style={styles.placeholder}>
{/* 加载占位符 */}
</View>
)}
{error && (
<View style={styles.errorPlaceholder}>
{/* 错误占位符 */}
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#f0f0f0',
},
image: {
position: 'absolute',
},
placeholder: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
},
errorPlaceholder: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ffebee',
},
});
export default OptimizedImage;
⚡ 启动时间优化
延迟加载组件
typescript
// src/components/LazyComponent.tsx
import React, {Suspense, lazy} from 'react';
import {View, Text, ActivityIndicator} from 'react-native';
// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const LazyLoadingExample: React.FC = () => {
return (
<View>
<Text>这是立即加载的内容</Text>
<Suspense
fallback={
<View style={{padding: 20, alignItems: 'center'}}>
<ActivityIndicator size="large" />
<Text>加载中...</Text>
</View>
}
>
<HeavyComponent />
</Suspense>
</View>
);
};
export default LazyLoadingExample;
代码分割和动态导入
typescript
// src/utils/dynamicImport.ts
export async function loadComponentDynamically<T>(
importFunction: () => Promise<{default: T}>
): Promise<T> {
try {
const module = await importFunction();
return module.default;
} catch (error) {
console.error('动态导入失败:', error);
throw error;
}
}
// 使用示例
const DynamicComponentLoader: React.FC = () => {
const [Component, setComponent] = useState<React.ComponentType | null>(null);
const [loading, setLoading] = useState(false);
const loadComponent = async () => {
setLoading(true);
try {
const LoadedComponent = await loadComponentDynamically(
() => import('./SomeHeavyComponent')
);
setComponent(() => LoadedComponent);
} catch (error) {
console.error('加载组件失败:', error);
} finally {
setLoading(false);
}
};
return (
<View>
{!Component && (
<Button title="加载组件" onPress={loadComponent} disabled={loading} />
)}
{loading && <ActivityIndicator />}
{Component && <Component />}
</View>
);
};
📦 包大小优化
Bundle 分析
bash
# 分析 Android bundle
npx react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output android-bundle.js \
--assets-dest android-assets
# 使用 bundle-analyzer 分析
npm install -g react-native-bundle-visualizer
npx react-native-bundle-visualizer
减少包大小的策略
typescript
// 1. 按需导入
// ❌ 不好的做法
import * as _ from 'lodash';
// ✅ 好的做法
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
// 2. 移除未使用的代码
// 使用 ESLint 规则检测未使用的导入
// eslint-disable-next-line no-unused-vars
// 3. 优化图片资源
// 使用 WebP 格式
// 压缩图片
// 使用矢量图标
// 4. 代码压缩配置
// metro.config.js
module.exports = {
transformer: {
minifierConfig: {
keep_fnames: true,
mangle: {
keep_fnames: true,
},
},
},
};
🔧 性能最佳实践
列表性能优化
typescript
// src/components/HighPerformanceList.tsx
import React, {useMemo, useCallback} from 'react';
import {FlatList, View, Text, StyleSheet} from 'react-native';
interface ListData {
id: string;
title: string;
subtitle: string;
}
const HighPerformanceList: React.FC<{data: ListData[]}> = ({data}) => {
// 使用 getItemLayout 提高滚动性能
const getItemLayout = useCallback(
(data: ListData[] | null | undefined, index: number) => ({
length: 80, // 固定高度
offset: 80 * index,
index,
}),
[]
);
// 优化的渲染函数
const renderItem = useCallback(({item}: {item: ListData}) => (
<ListItem item={item} />
), []);
// 键提取器
const keyExtractor = useCallback((item: ListData) => item.id, []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
// 性能优化配置
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={15}
windowSize={10}
// 避免重新创建样式对象
contentContainerStyle={styles.listContainer}
/>
);
};
// 优化的列表项组件
const ListItem = React.memo<{item: ListData}>(({item}) => (
<View style={styles.itemContainer}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subtitle}>{item.subtitle}</Text>
</View>
));
const styles = StyleSheet.create({
listContainer: {
paddingVertical: 10,
},
itemContainer: {
height: 80,
paddingHorizontal: 15,
paddingVertical: 10,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#eee',
justifyContent: 'center',
},
title: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: '#666',
},
});
export default HighPerformanceList;
网络请求优化
typescript
// src/services/optimizedAPI.ts
class OptimizedAPIService {
private cache = new Map<string, {data: any; timestamp: number}>();
private pendingRequests = new Map<string, Promise<any>>();
async get<T>(url: string, options: {
cache?: boolean;
cacheTTL?: number;
timeout?: number;
} = {}): Promise<T> {
const {cache = true, cacheTTL = 5 * 60 * 1000, timeout = 10000} = options;
// 检查缓存
if (cache) {
const cached = this.cache.get(url);
if (cached && Date.now() - cached.timestamp < cacheTTL) {
return cached.data;
}
}
// 检查是否有相同的请求正在进行
if (this.pendingRequests.has(url)) {
return this.pendingRequests.get(url);
}
// 创建请求
const request = this.makeRequest<T>(url, timeout);
this.pendingRequests.set(url, request);
try {
const data = await request;
// 缓存结果
if (cache) {
this.cache.set(url, {data, timestamp: Date.now()});
}
return data;
} finally {
this.pendingRequests.delete(url);
}
}
private async makeRequest<T>(url: string, timeout: number): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
clearCache(): void {
this.cache.clear();
}
}
export const optimizedAPI = new OptimizedAPIService();
🎉 本章小结
在这一章中,我们学习了:
- ✅ 性能监控工具的使用
- ✅ 渲染性能优化技巧
- ✅ 内存管理和泄漏预防
- ✅ 启动时间优化方法
- ✅ 包大小优化策略
- ✅ 列表和网络请求优化
- ✅ 性能最佳实践
📝 作业
- 使用性能监控工具分析现有应用的性能瓶颈
- 优化一个包含大量数据的列表组件
- 实现图片懒加载和缓存机制
- 分析并优化应用的启动时间
准备好学习应用打包和发布了吗?让我们继续第9章:打包与发布!