Appearance
性能优化
1. 性能优化简介
在 React Native 应用开发中,性能优化是一个非常重要的环节。良好的性能可以提升用户体验,减少应用崩溃的可能性,延长电池寿命。性能优化主要包括以下几个方面:
- 列表优化:优化长列表的渲染性能
- 图片优化:减少图片加载时间和内存占用
- 内存管理:避免内存泄漏和过度使用内存
- 渲染优化:减少不必要的渲染
- 网络优化:优化网络请求和数据处理
2. 列表优化
2.1 使用 FlatList 而非 ScrollView
对于长列表,应该使用 FlatList 而不是 ScrollView,因为 FlatList 实现了虚拟化,只渲染可见区域的项目。
javascript
// 不推荐:使用 ScrollView 渲染长列表
<ScrollView>
{items.map(item => (
<Item key={item.id} item={item} />
))}
</ScrollView>
// 推荐:使用 FlatList 渲染长列表
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => <Item item={item} />}
/>2.2 优化 FlatList
2.2.1 设置适当的窗口大小
javascript
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => <Item item={item} />}
windowSize={10} // 控制预加载的项目数量
maxToRenderPerBatch={5} // 每批渲染的项目数量
updateCellsBatchingPeriod={100} // 批处理更新的时间间隔
removeClippedSubviews={true} // 移除不可见的子视图
/>2.2.2 使用 getItemLayout
如果列表项的高度是固定的,使用 getItemLayout 可以避免测量每个项目的高度,提高性能。
javascript
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => <Item item={item} />}
getItemLayout={(data, index) => ({
length: 100, // 每个项目的高度
offset: 100 * index,
index,
})}
/>2.2.3 使用 initialNumToRender
设置 initialNumToRender 可以控制初始渲染的项目数量,减少初始加载时间。
javascript
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => <Item item={item} />}
initialNumToRender={10} // 初始渲染的项目数量
/>2.2.4 使用 shouldComponentUpdate 或 React.memo
对于列表项组件,使用 shouldComponentUpdate 或 React.memo 可以避免不必要的重新渲染。
javascript
// 使用 React.memo
const Item = React.memo(({ item }) => {
return (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
});
// 或者使用 shouldComponentUpdate
class Item extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.item.id !== nextProps.item.id ||
this.props.item.title !== nextProps.item.title;
}
render() {
const { item } = this.props;
return (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
}
}2.3 虚拟列表
对于非常长的列表,可以使用虚拟列表库,如 react-window 或 react-virtualized。
bash
npm install react-windowjavascript
import { FixedSizeList as List } from 'react-window';
const VirtualList = ({ items }) => {
const Row = ({ index, style }) => (
<div style={style}>
<Item item={items[index]} />
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={100}
width="100%"
>
{Row}
</List>
);
};3. 图片优化
3.1 图片格式选择
- PNG:适合图标和图形,支持透明
- JPG:适合照片,压缩率高
- WebP:Google 开发的格式,压缩率更高,支持透明
3.2 图片尺寸优化
- 使用适当尺寸的图片,避免使用过大的图片
- 为不同设备提供不同分辨率的图片
- 使用
resizeMode属性控制图片显示方式
javascript
<Image
source={require('./image.png')}
style={{ width: 100, height: 100 }}
resizeMode="cover" // 'cover', 'contain', 'stretch', 'repeat', 'center'
/>3.3 图片加载优化
3.3.1 使用 Image.getSize
在加载图片前获取图片尺寸,避免布局跳动。
javascript
Image.getSize(
'https://example.com/image.jpg',
(width, height) => {
console.log('图片尺寸:', width, height);
// 计算合适的显示尺寸
},
error => {
console.error('获取图片尺寸失败:', error);
}
);3.3.2 使用占位符
在图片加载完成前显示占位符,提升用户体验。
javascript
const [loaded, setLoaded] = useState(false);
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={{ width: 100, height: 100 }}
onLoad={() => setLoaded(true)}
>
{!loaded && (
<View style={{ width: 100, height: 100, backgroundColor: '#f0f0f0' }}>
<ActivityIndicator />
</View>
)}
</Image>3.3.3 图片缓存
使用图片缓存库,如 react-native-fast-image,提高图片加载速度。
bash
npm install react-native-fast-imagejavascript
import FastImage from 'react-native-fast-image';
<FastImage
source={{
uri: 'https://example.com/image.jpg',
priority: FastImage.priority.high,
}}
style={{ width: 100, height: 100 }}
resizeMode={FastImage.resizeMode.cover}
/>3.4 懒加载
对于长列表中的图片,使用懒加载可以减少初始加载时间和内存使用。
javascript
import { FlatList } from 'react-native';
import FastImage from 'react-native-fast-image';
const LazyImage = ({ uri, style }) => {
const [isVisible, setIsVisible] = useState(false);
return (
<View style={style}>
{isVisible ? (
<FastImage
source={{ uri }}
style={style}
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={[style, { backgroundColor: '#f0f0f0' }]} />
)}
<View
onViewableItemsChanged={({ viewableItems }) => {
if (viewableItems.length > 0) {
setIsVisible(true);
}
}}
viewabilityConfig={{
itemVisiblePercentThreshold: 50,
}}
/>
</View>
);
};
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<LazyImage uri={item.image} style={{ width: 100, height: 100 }} />
)}
/>4. 渲染优化
4.1 使用 React.memo
对于纯展示组件,使用 React.memo 可以避免不必要的重新渲染。
javascript
const MyComponent = React.memo(({ prop1, prop2 }) => {
return (
<View>
<Text>{prop1}</Text>
<Text>{prop2}</Text>
</View>
);
});4.2 使用 useCallback 和 useMemo
使用 useCallback 可以缓存函数,使用 useMemo 可以缓存计算结果。
javascript
const MyComponent = ({ items, onItemPress }) => {
// 缓存函数
const handlePress = useCallback((id) => {
onItemPress(id);
}, [onItemPress]);
// 缓存计算结果
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return (
<View>
<Text>Total: {total}</Text>
{items.map(item => (
<TouchableOpacity key={item.id} onPress={() => handlePress(item.id)}>
<Text>{item.name}</Text>
</TouchableOpacity>
))}
</View>
);
};4.3 避免在渲染过程中创建函数
在渲染过程中创建函数会导致每次渲染都创建新的函数实例,影响性能。
javascript
// 不推荐
const MyComponent = ({ items }) => {
return (
<View>
{items.map(item => (
<TouchableOpacity
key={item.id}
onPress={() => console.log('Pressed:', item.id)} // 每次渲染都会创建新函数
>
<Text>{item.name}</Text>
</TouchableOpacity>
))}
</View>
);
};
// 推荐
const MyComponent = ({ items }) => {
const handlePress = useCallback((id) => {
console.log('Pressed:', id);
}, []);
return (
<View>
{items.map(item => (
<TouchableOpacity
key={item.id}
onPress={() => handlePress(item.id)}
>
<Text>{item.name}</Text>
</TouchableOpacity>
))}
</View>
);
};4.4 避免在渲染过程中进行计算
在渲染过程中进行计算会导致每次渲染都重新计算,影响性能。
javascript
// 不推荐
const MyComponent = ({ items }) => {
const total = items.reduce((sum, item) => sum + item.value, 0); // 每次渲染都会重新计算
return (
<View>
<Text>Total: {total}</Text>
</View>
);
};
// 推荐
const MyComponent = ({ items }) => {
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return (
<View>
<Text>Total: {total}</Text>
</View>
);
};5. 内存管理
5.1 避免内存泄漏
- 清理定时器和监听器
- 清理网络请求
- 清理事件监听器
javascript
const MyComponent = () => {
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
const subscription = someEventEmitter.addListener('event', handleEvent);
return () => {
clearInterval(timer); // 清理定时器
subscription.remove(); // 清理监听器
};
}, []);
return <View />;
};5.2 优化状态管理
- 避免在状态中存储过大的数据
- 使用适当的状态管理库(如 Redux Toolkit)
- 合理设计状态结构
5.3 图片内存管理
- 及时释放不再需要的图片资源
- 使用适当的图片尺寸
- 避免在内存中缓存过多图片
6. 网络优化
6.1 批量请求
- 合并多个网络请求
- 使用 GraphQL 减少过度获取
- 实现请求缓存
6.2 延迟加载
- 实现数据的分页加载
- 按需加载数据
- 使用骨架屏提升用户体验
6.3 网络状态检测
- 检测网络状态变化
- 在网络不可用时提供离线体验
- 实现数据同步机制
javascript
import NetInfo from '@react-native-community/netinfo';
const MyComponent = () => {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
});
return unsubscribe;
}, []);
return (
<View>
{isConnected ? (
<Text>在线</Text>
) : (
<Text>离线</Text>
)}
</View>
);
};7. 最佳实践
- 使用适当的组件:根据场景选择合适的组件,如使用 FlatList 渲染长列表
- 优化渲染:使用 React.memo、useCallback、useMemo 等优化渲染性能
- 图片优化:选择合适的图片格式和尺寸,使用缓存和懒加载
- 内存管理:避免内存泄漏,及时清理资源
- 网络优化:批量请求,延迟加载,检测网络状态
- 代码分割:使用动态导入减少初始包大小
- 性能监控:使用 Flipper 和 Chrome DevTools 监控性能
- 定期测试:在不同设备上测试应用性能
8. 常见性能问题与解决方案
8.1 列表滚动卡顿
问题:长列表滚动时卡顿
解决方案:
- 使用 FlatList 而非 ScrollView
- 优化 FlatList 的属性
- 使用 React.memo 优化列表项
- 减少列表项的复杂度
8.2 图片加载慢
问题:图片加载时间长
解决方案:
- 使用适当尺寸的图片
- 使用 WebP 格式
- 使用图片缓存库
- 实现图片懒加载
8.3 应用启动慢
问题:应用启动时间长
解决方案:
- 减少初始包大小
- 使用代码分割
- 延迟加载非关键资源
- 优化初始渲染
8.4 内存占用过高
问题:应用内存占用过高
解决方案:
- 清理定时器和监听器
- 优化图片加载
- 合理管理状态
- 使用内存分析工具
9. 扩展阅读
10. 完整示例
10.1 优化后的列表
javascript
// OptimizedList.js
import React, { useCallback, useMemo } from 'react';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
const Item = React.memo(({ item, onPress }) => {
return (
<TouchableOpacity style={styles.item} onPress={() => onPress(item.id)}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.description}>{item.description}</Text>
</TouchableOpacity>
);
});
const OptimizedList = ({ items, onItemPress }) => {
const handlePress = useCallback((id) => {
onItemPress(id);
}, [onItemPress]);
const keyExtractor = useCallback((item) => item.id, []);
const getItemLayout = useCallback((data, index) => ({
length: 100,
offset: 100 * index,
index,
}), []);
const renderItem = useCallback(({ item }) => (
<Item item={item} onPress={handlePress} />
), [handlePress]);
return (
<FlatList
data={items}
keyExtractor={keyExtractor}
renderItem={renderItem}
getItemLayout={getItemLayout}
windowSize={10}
maxToRenderPerBatch={5}
updateCellsBatchingPeriod={100}
removeClippedSubviews={true}
initialNumToRender={10}
style={styles.container}
/>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#f9c2ff',
padding: 10,
marginVertical: 5,
marginHorizontal: 10,
height: 100,
},
title: {
fontSize: 18,
fontWeight: 'bold',
},
description: {
fontSize: 14,
color: '#666',
},
});
export default OptimizedList;10.2 优化后的图片加载
javascript
// OptimizedImage.js
import React, { useState } from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import FastImage from 'react-native-fast-image';
const OptimizedImage = ({ uri, style, placeholderColor = '#f0f0f0' }) => {
const [isLoading, setIsLoading] = useState(true);
return (
<View style={[style, isLoading && { backgroundColor: placeholderColor }]}>
<FastImage
source={{ uri }}
style={style}
resizeMode={FastImage.resizeMode.cover}
onLoadStart={() => setIsLoading(true)}
onLoadEnd={() => setIsLoading(false)}
/>
{isLoading && (
<View style={[StyleSheet.absoluteFillObject, styles.loadingContainer]}>
<ActivityIndicator size="small" color="#999" />
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
loadingContainer: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
},
});
export default OptimizedImage;10.3 性能监控示例
javascript
// PerformanceMonitor.js
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { usePerformanceMonitor } from '@react-native-firebase/perf';
const PerformanceMonitor = () => {
const perf = usePerformanceMonitor();
const [traceResults, setTraceResults] = useState([]);
const runPerformanceTest = async () => {
// 创建性能跟踪
const trace = perf.newTrace('heavy-operation');
try {
trace.start();
// 执行耗时操作
await new Promise(resolve => {
let result = 0;
for (let i = 0; i < 10000000; i++) {
result += i;
}
setTimeout(resolve, 100);
});
trace.stop();
// 获取跟踪结果
const metrics = await trace.getMetrics();
setTraceResults(prev => [...prev, metrics]);
} catch (error) {
console.error('性能测试失败:', error);
trace.stop();
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>性能监控示例</Text>
<Button title="运行性能测试" onPress={runPerformanceTest} />
<View style={styles.results}>
{traceResults.map((result, index) => (
<View key={index} style={styles.resultItem}>
<Text>测试 #{index + 1}</Text>
<Text>执行时间: {result.duration ? result.duration.toFixed(2) : 'N/A'} ms</Text>
</View>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
results: {
marginTop: 20,
},
resultItem: {
backgroundColor: '#f0f0f0',
padding: 10,
marginBottom: 10,
borderRadius: 5,
},
});
export default PerformanceMonitor;这些示例展示了如何优化列表渲染、图片加载和监控应用性能,帮助开发者构建更流畅、更响应的 React Native 应用。
