React Native 性能不是只看 React 重新渲染。移动端还要关注 JS 线程、UI 线程、原生模块调用、列表虚拟化、图片内存、动画流畅度和启动时间。本章覆盖从性能基础到 New Architecture 的专家能力。
1. 线程模型
React Native 传统心智中常见线程:
- JS Thread:运行 JavaScript、React 渲染、业务逻辑。
- UI/Main Thread:原生 UI 绘制和交互。
- Native Modules Thread:部分原生模块工作。
- Shadow Tree / Layout:布局计算相关工作。
如果 JS Thread 被长任务阻塞,点击、导航、列表更新可能卡顿。若动画依赖 JS 每帧驱动,也会掉帧。
2. 性能指标
移动端关注:
- 冷启动时间。
- 首屏可交互时间。
- JS bundle 体积。
- 列表滚动 FPS。
- 导航切换耗时。
- 图片内存。
- 崩溃率。
- ANR / 卡死。
- 电量和发热。
3. 常见性能问题
ScrollView渲染长列表。FlatList配置不当。renderItem不稳定。- 列表项太复杂。
- 图片过大。
- JS 同步计算太重。
- 频繁跨 JS/native 边界。
- Context 高频更新。
- 动画跑在 JS 线程。
- 启动时加载过多模块。
4. FlatList 优化
jsx
const renderItem = useCallback(({ item }) => {
return <LessonCard item={item} />;
}, []);
const keyExtractor = useCallback((item) => item.id, []);
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
initialNumToRender={8}
maxToRenderPerBatch={8}
windowSize={7}
removeClippedSubviews
/>
固定高度:
jsx
const ITEM_HEIGHT = 88;
<FlatList
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
列表优化顺序:
- 用
FlatList替代长ScrollView。 - 稳定
renderItem和keyExtractor。 - 简化列表项。
- 使用
getItemLayout。 - 调整 batch/window 参数。
- 必要时使用 FlashList。
5. React.memo
jsx
const LessonCard = memo(function LessonCard({ item, onPress }) {
return (
<Pressable onPress={() => onPress(item.id)} style={styles.card}>
<Text>{item.title}</Text>
</Pressable>
);
});
要让 memo 有效,传入 Props 要稳定。
错误:
jsx
<LessonCard item={{ ...item }} onPress={() => open(item.id)} />
6. useMemo / useCallback
jsx
const visibleItems = useMemo(
() => filterLessons(items, query, level),
[items, query, level],
);
const handlePress = useCallback((id) => {
navigation.navigate('Detail', { id });
}, [navigation]);
不要盲目使用。优化前先用 Profiler、React Native DevTools 或 Flipper 定位。
7. InteractionManager
把非紧急任务延后到交互之后。
jsx
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
warmUpSearchIndex();
});
return () => task.cancel();
}, []);
适合导航动画后再执行重计算。
8. 图片性能
图片问题常见于移动端:
- 原图过大。
- 列表头像重复加载。
- 没有占位图。
- 图片尺寸不明确。
- 缓存策略差。
基础:
jsx
<Image
source={{ uri: item.cover }}
style={{ width: 96, height: 72, borderRadius: 8 }}
resizeMode="cover"
/>
专家实践:
- 服务端裁剪。
- WebP/AVIF 支持视平台而定。
- 缩略图优先。
- 列表中避免大图。
- 使用成熟图片库处理缓存。
9. Animated
React Native 内置 Animated:
jsx
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 220,
useNativeDriver: true,
}).start();
}, [opacity]);
return <Animated.View style={{ opacity }} />;
useNativeDriver: true 能把支持的动画交给原生侧,减少 JS 线程影响。
限制:
- 不是所有样式都支持 native driver。
- 布局相关动画通常更复杂。
10. Reanimated
复杂手势和高性能动画常用 Reanimated。
概念:
jsx
const translateX = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
优势:
- 动画逻辑运行在 UI 线程。
- 更适合手势驱动动画。
- 与 Gesture Handler 配合好。
11. Gesture Handler
复杂手势不要只靠 Pressable。
常见手势:
- Pan。
- Tap。
- LongPress。
- Pinch。
- Swipe。
手势设计要考虑:
- 与 ScrollView 冲突。
- Android/iOS 手势差异。
- 触摸区域。
- 可访问性替代操作。
12. LayoutAnimation
简单布局动画:
jsx
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setExpanded((value) => !value);
适合简单展开收起。复杂场景用 Reanimated。
13. 启动性能
启动优化:
- 减少启动时同步计算。
- 减少首屏依赖。
- 延迟加载低频模块。
- 优化图片和字体。
- 使用 Hermes。
- 控制 bundle 体积。
- 避免启动时请求阻塞首屏。
14. Hermes
Hermes 是 React Native 常用 JavaScript 引擎,优化启动和内存表现。
关注点:
- 与 RN 版本兼容。
- Debug 和 Release 表现不同。
- 性能测试以 Release 包为准。
15. Metro
Metro 是 React Native bundler。
常见能力:
- 模块解析。
- 平台文件选择:
.ios.js/.android.js。 - Babel 转换。
- 资源打包。
专家实践:
- 控制依赖体积。
- 避免引入 Node-only 包。
- 配置 monorepo watchFolders。
- 使用 bundle 分析工具。
16. New Architecture
React Native New Architecture 包括:
- Fabric Renderer:新渲染系统。
- TurboModules:新原生模块系统。
- JSI:JavaScript Interface,减少传统 bridge 开销。
- Codegen:类型安全地生成 JS/native 绑定。
官方在 RN 0.76 起默认启用新架构,并带来现代 React 能力支持,如 Suspense、Transitions、automatic batching、useLayoutEffect 等。
17. 旧 Bridge 的限制
传统 Bridge 特点:
text
JS <-> serialized async bridge <-> Native
限制:
- 通信需要序列化。
- 高频调用成本高。
- 同步能力受限。
- 类型边界弱。
18. JSI
JSI 允许 JS 引擎和原生对象更直接交互。
价值:
- 避免大量 JSON 序列化。
- 支持同步调用场景。
- 为高性能库提供底层能力。
典型使用者:
- Reanimated。
- MMKV。
- Vision Camera。
19. TurboModules
新原生模块系统:
- 懒加载模块。
- 类型安全。
- Codegen 生成绑定。
- 更少 bridge 开销。
适合封装平台能力,例如加密、传感器、SDK。
20. Fabric
Fabric 是新渲染器:
- 与 React 并发特性更好集成。
- 更一致的布局和渲染路径。
- 支持更现代的原生组件系统。
对应用开发者:
- 大多数业务代码不用直接操作 Fabric。
- 依赖库需要兼容新架构。
- 升级时重点检查原生库兼容。
21. Codegen
Codegen 根据类型定义生成 native binding。
价值:
- 类型安全。
- 减少手写桥接。
- JS 和 Native API 一致。
专家实践:
- Native Module API 先设计稳定。
- 入参和返回值保持可序列化或受支持类型。
- 错误模型明确。
22. Native Module 何时需要
需要原生模块的情况:
- JS 无法访问的平台能力。
- 性能关键逻辑。
- 第三方原生 SDK。
- 加密、安全存储、媒体处理。
- 复杂传感器和硬件交互。
不需要:
- 普通业务状态。
- 可以用现有 Expo/community 包解决。
- 只是为了"高级"。
23. 性能排查顺序
- 用 Release 包复现。
- 确认是 JS 卡、UI 卡、网络慢、图片慢还是原生模块慢。
- 检查列表。
- 检查图片。
- 检查启动依赖。
- 检查频繁 setState / Context 更新。
- 检查原生库和新架构兼容。
- 做小范围优化并验证指标。
24. 专家性能清单
- 长列表不使用 ScrollView。
- 图片有尺寸、缓存、缩略图策略。
- 动画尽量不依赖 JS 每帧驱动。
- 首屏不加载低频模块。
- Context 不承载高频变化。
- 大计算延后或移出 JS 主路径。
- 新架构升级前检查依赖兼容。
- 性能测试使用 Release 构建。
25. FlatList 问题排查库
问题:滚动卡顿。
排查:
- 是否使用了
ScrollView? renderItem是否每次创建复杂闭包?- item 是否包含大图、阴影、复杂 SVG?
- 是否缺少
keyExtractor? - 是否可以使用
getItemLayout? initialNumToRender是否过大?- 是否在列表滚动时 setState?
优化:
jsx
const LessonRow = memo(function LessonRow({ item, onPress }) {
return <Pressable onPress={() => onPress(item.id)}>{/* ... */}</Pressable>;
});
26. 动画性能扩展
动画分三类:
- 简单透明度和位移:Animated + native driver。
- 手势驱动:Reanimated + Gesture Handler。
- 布局展开收起:LayoutAnimation 或 Reanimated Layout Animations。
反模式:
jsx
setInterval(() => {
setPosition((x) => x + 1);
}, 16);
这会让 JS 每帧 setState,容易卡顿。
27. 启动性能扩展
启动阶段不要做:
- 大量 JSON parse。
- 同步初始化多个 SDK。
- 立即加载低频页面。
- 首屏前请求太多接口。
- 渲染隐藏的大组件树。
启动优化路线:
- 首屏最小化。
- SDK 延迟初始化。
- 低频模块 lazy。
- 图片和字体优化。
- Release 包测量。
28. 新架构迁移风险
迁移前检查:
- 所有原生依赖是否支持 New Architecture。
- 是否使用旧 Native Module。
- 是否依赖不兼容的 UI 组件库。
- iOS Pods 和 Android Gradle 是否支持。
- CI 是否能构建新架构。
迁移策略:
- 单独分支验证。
- 先升级 RN 版本。
- 再启用新架构。
- 保留回退开关。
- 优先修复核心路径。
29. 原生模块设计原则
Native Module API 应:
- 小而稳定。
- 参数类型明确。
- 错误模型明确。
- 避免高频跨边界调用。
- 支持取消或超时。
错误:
js
NativeModule.doEverything(bigObject);
更好:
js
NativeCrypto.encrypt({ text, keyId });
NativeCrypto.decrypt({ payload, keyId });
30. 性能专家题
- 卡顿发生在 JS 线程还是 UI 线程?
- Debug 包和 Release 包表现是否一致?
- FlatList item 是否过重?
- 动画是否依赖 JS 每帧执行?
- 图片是否经过裁剪和缓存?
- 新架构是否改变了依赖兼容性?
- 是否有启动阶段同步重任务?
31. 性能知识点索引
- JS Thread 阻塞会影响交互。
- UI Thread 掉帧会影响动画。
- Debug 包性能不可信。
- Release 包才是性能依据。
- FlatList 参数需要按场景调。
- FlashList 可用于超大列表。
- 图片内存是移动端大头。
- 动画应尽量跑 UI 线程。
- Reanimated 适合复杂手势动画。
- Gesture Handler 处理手势冲突。
- InteractionManager 延后非紧急任务。
- Hermes 优化启动和内存。
- Metro 影响模块打包。
- JSI 减少传统 bridge 开销。
- TurboModules 提供新模块系统。
- Fabric 是新渲染器。
- Codegen 提高 native binding 类型安全。
- 新架构依赖兼容必须检查。
- 启动阶段要减少同步任务。
- 性能优化必须有指标对比。
32. 新架构专家问题
- 当前依赖是否全部支持 New Architecture?
- 是否存在旧 bridge 高频调用?
- Native Module API 是否过大?
- 是否需要同步 native 能力?
- Codegen 类型是否覆盖错误模型?
- Fabric 迁移是否影响自定义组件?
- 回退策略是什么?
面试题完整答案总集:React Native 性能、动画与新架构
卡顿发生在 JS 线程还是 UI 线程,如何判断?
如果点击响应、状态更新、列表渲染慢,通常怀疑 JS 线程;如果动画和滚动掉帧但 JS 日志不多,可能是 UI 线程或原生绘制压力。应在 Release 包中使用 Profiler、Flipper、Xcode Instruments、Android Profiler 等工具定位,而不是凭感觉判断。
Debug 包和 Release 包表现是否一致?
通常不一致。Debug 包有开发工具、日志、未优化打包和调试开销,性能不能代表真实用户体验。性能结论必须基于 Release 或接近 Release 的构建。
FlatList item 是否过重会造成什么问题?
列表项如果包含大图、复杂阴影、嵌套过深、频繁状态更新或匿名函数,会导致滚动掉帧和内存增长。优化方式包括 memo 列表项、稳定 renderItem、简化布局、固定行高、缩略图和合理 FlatList 参数。
动画是否应该依赖 JS 每帧执行?
不应该。JS 每帧 setState 容易被业务逻辑阻塞,导致动画掉帧。简单动画可用 Animated native driver,复杂手势动画优先用 Reanimated,让动画逻辑运行在 UI 线程。
图片为什么是移动端性能重点?
图片会占用网络、解码时间和内存。列表中加载原图会导致卡顿和内存压力。应使用服务端裁剪、缩略图、明确尺寸、缓存策略、占位图和失败状态。
新架构会改变哪些风险?
新架构带来 Fabric、TurboModules、JSI、Codegen 等能力,但也要求原生依赖兼容。迁移风险包括旧 Native Module 不兼容、UI 组件库不支持 Fabric、Pods/Gradle 配置问题、CI 构建失败。迁移前必须检查依赖和保留回退方案。
启动阶段同步重任务为什么危险?
启动阶段如果同步解析大 JSON、初始化多个 SDK、加载低频模块,会延迟首屏和可交互时间。应最小化首屏依赖,把非关键任务延迟到交互后或后台执行。
Native Module API 应如何设计?
API 应小而稳定,参数和返回值类型明确,错误模型清晰,避免高频跨边界调用。原生模块应封装平台能力,不应随某个页面业务频繁变化。复杂任务应支持取消、超时和错误上报。