Chapter 08

性能优化

理解 JS/UI/Shadow 三线程模型,用 Flipper 找到真正的性能瓶颈,系统性优化 FlatList、渲染、启动速度。

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 提供多尺寸版本(为列表缩略图请求小图,详情页请求大图)。