RN 性能问题的根源
React Native 的性能问题大多源于一个核心矛盾:JavaScript 是单线程的,而移动应用的渲染需要每秒 60 帧(高刷屏 120 帧)的更新频率,每帧只有约 16ms(或 8ms)。当 JS 线程在某一帧内耗时超过这个阈值,就会掉帧,用户感知为卡顿。
常见的 JS 线程性能杀手包括:在渲染函数里进行复杂计算、频繁创建新对象(内联样式、内联函数)、不必要的组件重渲染、大量图片解码、同步读取 AsyncStorage 等。
核心名词解释
JS Thread(JavaScript 线程)
运行 React 渲染逻辑、业务代码、状态管理的主线程。CPU 密集任务会阻塞此线程,导致动画卡顿和 UI 无响应。
UI Thread(原生 UI 线程)
操作系统的主线程,负责绘制屏幕像素、处理触摸事件。动画通过 useNativeDriver 或 Reanimated 在此线程运行时,即使 JS 线程繁忙也不掉帧。
Shadow Thread(布局线程)
运行 Yoga 布局引擎的后台线程,负责将 Flexbox 样式计算为具体的坐标和尺寸,然后同步给 UI 线程渲染。
FPS(每秒帧数)
动画流畅度指标。60fps = 每帧 16.7ms,120fps = 每帧 8.3ms。低于 60fps 时用户开始感知卡顿,低于 30fps 时明显不流畅。
Hermes
Meta 专为 React Native 开发的 JavaScript 引擎,采用 AOT(Ahead-of-Time)字节码编译,相比 V8 显著减少首屏启动时间和内存占用,是 RN 0.64+ 的默认引擎。
React.memo
高阶组件,对函数组件进行浅层 props 比较,如果 props 没有变化则跳过重渲染。用于 FlatList 的 renderItem 组件可避免列表滚动时的大量无效渲染。
useMemo / useCallback
useMemo 缓存计算结果(避免重复昂贵计算),useCallback 缓存函数引用(避免子组件因函数引用变化触发重渲染)。只在有明确性能问题时使用,不要过度使用。
VirtualizedList
FlatList 和 SectionList 的底层实现,实现了列表虚拟化(只渲染可见区域附近的 item),是 RN 大列表性能的核心。
三线程模型
React Native 三线程模型
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────────────────────┐
│ JS Thread │
│ • React 渲染(reconciliation) │
│ • 业务逻辑、网络请求处理 │
│ • 事件处理器 │
│ • 状态管理(Zustand/React Query) │
│ │
│ ⚠️ 这里慢 = 动画卡顿 + UI 无响应 │
└──────────────┬──────────────────────────────────┘
│ Bridge / JSI(发送 UI 指令)
▼
┌─────────────────────────────────────────────────┐
│ Shadow Thread(Yoga 布局) │
│ • 计算 Flexbox 布局(坐标/尺寸) │
│ • 异步,不阻塞 JS 或 UI 线程 │
└──────────────┬──────────────────────────────────┘
│(布局结果)
▼
┌─────────────────────────────────────────────────┐
│ UI Thread │
│ • 绘制原生组件 │
│ • 处理触摸/手势事件 │
│ • Reanimated worklet 动画 │
│ • useNativeDriver 动画 │
│ │
│ ✅ 这里始终流畅(操作系统保证) │
└─────────────────────────────────────────────────┘
FlatList 深度优化
import { FlatList, View, Text, StyleSheet } from 'react-native';
import { memo, useCallback } from 'react';
interface Item { id: string; text: string; height: number; }
// 1. renderItem 组件用 memo 包裹,避免列表父组件重渲染时重渲所有 item
const ListItem = memo(({ item }: { item: Item }) => (
<View style={[styles.item, { height: item.height }]}>
<Text>{item.text}</Text>
</View>
));
export function OptimizedList({ data }: { data: Item[] }) {
// 2. useCallback 稳定 renderItem 函数引用
const renderItem = useCallback(
({ item }: { item: Item }) => <ListItem item={item} />,
[]
);
// 3. getItemLayout:告知 FlatList 每个 item 的精确高度
// 避免 FlatList 测量 item 高度的开销
// 仅适用于固定高度的 item!
const ITEM_HEIGHT = 80;
const getItemLayout = useCallback(
(_: any, index: number) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}),
[]
);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
getItemLayout={getItemLayout} // 固定高度时使用,性能大幅提升
initialNumToRender={10} // 首屏渲染数量(不要设太大)
windowSize={5} // 渲染窗口(默认21,缩小节省内存)
maxToRenderPerBatch={5} // 每批次渲染数量
updateCellsBatchingPeriod={30} // 批次更新间隔(ms)
removeClippedSubviews={true} // Android:裁剪屏幕外视图
// 不要用这个!会重置滚动位置
// legacyImplementation={false}
/>
);
}
Hermes 引擎与启动优化
Hermes 是 Meta 专为 React Native 设计的 JavaScript 引擎。传统引擎(如 V8、JavaScriptCore)在应用启动时需要:加载 JS Bundle → 解析 JS 文本 → 编译为字节码 → 执行。这个过程在低端 Android 设备上可能需要 2-3 秒。
Hermes 采用 AOT(提前编译)策略:在构建时就将 JS 代码编译为字节码,应用启动时直接执行字节码,省去了运行时编译的时间。实测可将 TTI(Time to Interactive)减少 40-50%。
优化的优先级
不要过早优化。正确的流程是:1)发现性能问题;2)用 Flipper + React DevTools Profiler 定位具体瓶颈;3)针对性优化。盲目添加 memo、useCallback 不仅没用,反而会因为额外的比较开销降低性能。特别是简单组件,memo 的 props 浅比较开销可能大于重渲染本身。
图片优化
图片是移动应用最常见的性能问题来源之一。最佳实践:使用 WebP 格式(比 JPEG 小 25-35%)、为图片生成 blurhash 占位符(expo-image 内置支持)、按需加载(FlatList 配合 expo-image 的懒加载)、CDN 提供多尺寸版本(为列表缩略图请求小图,详情页请求大图)。