5月27日 18:04

如何优化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 年的推荐选择。

typescript
import { 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 组件优势明显。

typescript
import { 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 调用。

typescript
import { 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 线程,彻底消除动画卡顿。

typescript
import 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 线程参与,页面切换延迟从数十毫秒降至接近原生水平。

typescript
import { 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 等待交互完成后再执行:

typescript
import { 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 ProfilerJS 函数级耗时分析开发
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

优化前先测量,优化后对比验证。没有量化数据的优化只是在猜测。

标签:Expo