如何优化Expo应用的性能?有哪些常见的性能问题?
组件渲染优化
React Native 中最常见性能问题就是不必要渲染。通过 React.memo、useMemo 和 useCallback 三个核心 API 可以有效控制渲染范围。
typescript// React.memo:对 props 做浅比较,避免父组件更新时子组件跟随重渲染 const ListItem = React.memo<{ item: Item }>(({ item }) => { return <Text>{item.title}</Text>; }); // useMemo:缓存计算结果,避免每次渲染重复执行昂贵运算 function SortedList({ items }: { items: Item[] }) { const sorted = useMemo( () => [...items].sort((a, b) => a.priority - b.priority), [items] ); return <FlatList data={sorted} renderItem={({ item }) => <ListItem item={item} />} />; } // useCallback:稳定函数引用,避免因函数重建导致子组件重渲染 function Parent() { const [count, setCount] = useState(0); const handlePress = useCallback(() => { setCount((c) => c + 1); }, []); return <Child onPress={handlePress} />; }
需要注意:memo 和 useMemo 不是越多越好。对于 props 简单或渲染成本低的组件,浅比较本身的开销可能反而更高。建议先用 React DevTools Profiler 定位瓶颈,再针对性优化。
列表渲染优化
FlashList 替代 FlatList
FlatList 是 React Native 内置的虚拟化列表组件,但在长列表场景下性能不够理想。Shopify 开源的 FlashList 提供了约 10 倍的列表渲染性能提升,已成为 2026 年的推荐选择。
typescriptimport { FlashList } from '@shopify/flash-list'; <FlashList data={items} renderItem={({ item }) => <ListItem item={item} />} estimatedItemSize={64} // 必填:提供预估行高,用于滚动条计算 keyExtractor={(item) => item.id} />
如果仍使用 FlatList
某些场景下 FlatList 仍有其适用性,关键优化属性如下:
typescript<FlatList data={items} renderItem={({ item }) => <ListItem item={item} />} keyExtractor={(item) => item.id} removeClippedSubviews={true} // 移除屏幕外原生视图,降低内存 maxToRenderPerBatch={10} // 每批渲染数量,越小越不容易卡顿 windowSize={10} // 渲染窗口倍数,默认 21 initialNumToRender={10} // 首屏渲染数量 getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index, })} />
getItemLayout 是最容易被忽略但效果最显著的优化项。当列表项高度固定时,提供该属性可以让 FlatList 跳过异步布局计算,直接定位滚动位置,滚动性能提升明显。
启用新架构(New Architecture)
新架构是 React Native 近年来最重要的架构升级,包含 Fabric(新渲染系统)、TurboModules(新原生模块系统)和 JSI(JavaScript Interface)三个核心组件。Expo SDK 50+ 已默认支持。
性能提升数据:
- 冷启动速度提升约 40%
- 渲染速度提升约 35%
- 内存占用降低约 25%
- JS 调用原生方法的延迟降低约 40 倍
截至 2026 年初,超过 83% 的 EAS Build 项目已使用新架构。
typescript// app.json 中启用新架构 { "expo": { "newArchEnabled": true } }
迁移前建议使用 npx expo-doctor 检查第三方库兼容性,逐步升级避免一次性大改造带来的风险。
Hermes 引擎优化
Hermes 是 React Native 0.70+ 的默认 JavaScript 引擎,相比旧版 JSC 有显著性能优势:
- 启动速度提升约 40%(预编译字节码)
- 内存占用降低约 30%
- 包体积减小约 40%
typescript// app.json 确认 Hermes 已启用(0.70+ 默认开启) { "expo": { "jsEngine": "hermes" } }
Expo SDK 55+ 集成了 Hermes V1,进一步改善了 GC 表现和调试体验。如果项目仍在使用旧版 SDK,升级到 SDK 55+ 是最低成本的启动优化方案。
图片优化
expo-image 是 Expo 官方推荐的高性能图片组件,内置内存和磁盘缓存、占位符、渐变过渡等功能,相比 React Native 内置 Image 组件优势明显。
typescriptimport { Image } from 'expo-image'; <Image source={{ uri: 'https://example.com/photo.webp' }} style={{ width: 200, height: 200 }} cachePolicy="memory-disk" // 内存 + 磁盘二级缓存 contentFit="cover" transition={200} // 淡入动画时长 placeholder={blurhash} // 可选:加载前显示模糊占位 />
关键优化点:
| 策略 | 效果 | 实施难度 |
|---|---|---|
| 使用 WebP 格式 | 带宽减少约 70% | 低 |
| 启用缓存策略 | 重复加载耗时接近 0 | 低 |
| 按需加载尺寸 | 避免加载 4K 图显示缩略图 | 低 |
| 懒加载列表图片 | 减少首屏请求数 | 中 |
网络请求优化
缓存与去重
使用 TanStack Query(原 React Query)可以统一管理请求缓存、去重和状态,减少约 80% 的冗余 API 调用。
typescriptimport { useQuery } from '@tanstack/react-query'; function UserProfile({ userId }: { userId: string }) { const { data, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, // 5 分钟内视为新鲜数据 gcTime: 10 * 60 * 1000, // 10 分钟后清除缓存(原 cacheTime) }); if (isLoading) return <LoadingSpinner />; if (error) return <ErrorMessage error={error} />; return <Text>{data.name}</Text>; }
其他网络优化手段
- 请求批处理:将多个独立请求合并为一个批量接口,减少网络往返
- 响应压缩:服务端启用 Gzip/Brotli,响应体积减少 60%–80%
- 断点续传:大文件下载支持恢复,避免因网络波动从头开始
动画优化
React Native Reanimated 是高性能动画的标准方案,其 worklet 机制将动画计算从 JS 线程转移到 UI 线程,彻底消除动画卡顿。
typescriptimport Animated, { useSharedValue, useAnimatedStyle, withTiming, withSpring, } from 'react-native-reanimated'; function FadeInView({ children }: { children: React.ReactNode }) { const opacity = useSharedValue(0); const translateY = useSharedValue(20); const animatedStyle = useAnimatedStyle(() => ({ opacity: withTiming(opacity.value, { duration: 500 }), transform: [{ translateY: withSpring(translateY.value) }], })); useEffect(() => { opacity.value = 1; translateY.value = 0; }, []); return <Animated.View style={animatedStyle}>{children}</Animated.View>; }
核心原则:凡是影响视觉流畅度的动画(拖拽、滑动、转场),都应该使用 Reanimated 的 worklet 跑在 UI 线程,而非通过 JS 线程的 Animated 驱动。
导航性能
使用 @react-navigation/native-stack 替代 JS 实现的 stack 导航器。native-stack 直接使用 iOS 的 UINavigationController 和 Android 的 Fragment 过渡动画,导航过程中完全不需要 JS 线程参与,页面切换延迟从数十毫秒降至接近原生水平。
typescriptimport { createNativeStackNavigator } from '@react-navigation/native-stack'; const Stack = createNativeStackNavigator(); function App() { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Detail" component={DetailScreen} /> </Stack.Navigator> </NavigationContainer> ); }
内存管理
内存泄漏是 React Native 应用性能劣化的常见原因,主要集中在三个场景:未清理的订阅、未清除的定时器、未取消的网络请求。
typescript// 取消网络请求 useEffect(() => { const controller = new AbortController(); fetchUser(userId, { signal: controller.signal }); return () => controller.abort(); }, [userId]); // 清理定时器 useEffect(() => { const timer = setInterval(() => syncData(), 30000); return () => clearInterval(timer); }, []); // 清理事件订阅 useEffect(() => { const subscription = EventEmitter.addListener('onUpdate', handleUpdate); return () => subscription.remove(); }, []);
对于需要延迟执行的繁重任务,使用 InteractionManager 等待交互完成后再执行:
typescriptimport { InteractionManager } from 'react-native'; useEffect(() => { const task = InteractionManager.runAfterInteractions(() => { // 用户交互完成后再执行耗时操作 processHeavyData(); }); return () => task.cancel(); }, []);
内存检测工具:
- 开发环境:Flipper + React DevTools
- iOS:Xcode Instruments(Allocations / Leaks)
- Android:Android Studio Profiler
- 生产环境:Sentry 性能监控
Bundle 优化
减少应用包体积直接影响下载转化率和启动速度。
typescript// 移除生产环境 console 输出 // babel.config.js module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], env: { production: { plugins: ['transform-remove-console'], }, }, }; };
其他 Bundle 优化策略:
- Metro tree-shaking:确保未使用的导出被移除(Expo SDK 50+ 默认启用)
- Hermes 字节码预编译:构建时将 JS 编译为字节码,跳过运行时解析
- 审查依赖体积:使用
npx react-native-bundle-visualizer分析各模块占比,移除或替换体积过大的包 - 按需加载原生模块:Expo 的模块自动链接机制会引入所有已安装的原生模块,定期清理
package.json中未使用的依赖
性能监控与分析工具
| 工具 | 用途 | 适用阶段 |
|---|---|---|
| React DevTools Profiler | 分析组件渲染次数和耗时 | 开发 |
| Flipper | 网络监控、布局检查、内存分析 | 开发 |
| Expo DevTools (SDK 52+) | 实时性能检查、Bundle 分析、加载追踪 | 开发 |
| Hermes Sampling Profiler | JS 函数级耗时分析 | 开发 |
| Sentry | 生产环境性能监控和错误追踪 | 生产 |
| Firebase Performance | 生产环境启动时间、网络延迟监控 | 生产 |
typescript// Expo SDK 52+ 内置性能检查 // 在开发菜单中启用 "Performance Monitor" 即可查看: // - FPS 和帧时间 // - JS 线程和 UI 线程的 CPU 占用 // - 内存使用趋势
常见性能问题与解决方案
列表滚动卡顿
- 使用 FlashList 替代 FlatList(首选方案)
- 如果必须用 FlatList,提供
getItemLayout并启用removeClippedSubviews - 列表项组件用
React.memo包裹,确保renderItem函数引用稳定
应用启动慢
- 确认已启用新架构和 Hermes 引擎
- 移除启动阶段不必要的同步初始化代码
- 使用
InteractionManager.runAfterInteractions延迟非关键任务 - 生产环境移除
console.log(babel 插件)
图片加载慢
- 使用 expo-image 并开启
cachePolicy="memory-disk" - 服务端提供 WebP 格式和多种尺寸
- 列表中的图片使用懒加载
内存泄漏
- useEffect 返回清理函数,取消网络请求、定时器和订阅
- 使用
InteractionManager延迟非关键任务 - 定期使用 Xcode Instruments 或 Android Profiler 检查内存趋势
导航切换延迟
- 使用
native-stack替代 JS stack 导航器 - 减少导航页面中的
useEffect同步操作 - 页面组件使用
React.memo避免路由变化时无关组件重渲染
性能优化参考指标
建立可量化的性能目标,避免凭感觉优化:
| 指标 | 目标值(中端 Android) | 目标值(iPhone 13+) |
|---|---|---|
| 冷启动时间 | < 2.0s | < 1.2s |
| 持续滚动 FPS | >= 58 | >= 59 |
| 交互响应延迟 | < 100ms | < 50ms |
| JS 堆内存 | < 180MB | < 150MB |
| 安装包体积 | < 30MB | < 30MB |
优化前先测量,优化后对比验证。没有量化数据的优化只是在猜测。