如果你说的是 React Native 性能优化 + 常见坑,面试和实际开发里可以重点掌握下面这些。

1. RN 性能主要卡在哪里?

先建立一个简单模型:

JS Thread
   │
   ├── React Render
   ├── State 更新
   ├── 网络/业务逻辑
   │
   ↓
Bridge / JSI
   ↓
Native
   │
   ├── UI
   ├── Layout
   └── GPU

常见问题基本分成:

  • JS Thread 卡顿

  • 渲染次数过多

  • 大列表性能差

  • 图片太大

  • Native ↔ JS 通信过多

  • 动画掉帧

  • 内存泄漏

  • 首屏加载慢

  • Bundle 太大


2. 最重要:避免无意义的 Render

例如:

const Item = ({ item }) => {
  return <Text>{item.name}</Text>
}

父组件频繁更新时,Item 也可能跟着重新渲染。

可以:

const Item = React.memo(({ item }) => {
  return <Text>{item.name}</Text>
})

但不要看到组件就无脑 React.memo

因为:

<Item
  data={{ name: 'Tom' }}
/>

每次 render 都创建新对象:

第一次:{ name: 'Tom' } ← A
第二次:{ name: 'Tom' } ← B

引用不同,memo 还是会重新渲染。

可以:

const data = useMemo(
  () => ({ name: 'Tom' }),
  []
)

3. useCallback 也不要乱用

常见代码:

const handlePress = useCallback(() => {
  console.log(id)
}, [id])

如果子组件用了:

React.memo(...)

这种优化才比较有意义。

否则:

useCallback
    ↓
保存函数
    ↓
依赖检查
    ↓
增加代码复杂度

不一定比重新创建函数更快。

面试可以说:

useMemo/useCallback 是性能优化工具,不是默认编码规范,应该在存在实际 render 或计算成本时使用。


4. FlatList 是 RN 性能优化的核心

这是非常高频的面试题。

不要:

<ScrollView>
  {data.map(item => (
    <Item item={item} />
  ))}
</ScrollView>

大量数据应该:

<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={item => String(item.id)}
/>

因为 ScrollView 会一次性创建大量内容,而 FlatList 支持虚拟化。


FlatList 常用优化

<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
  initialNumToRender={10}
  maxToRenderPerBatch={10}
  windowSize={5}
  removeClippedSubviews
/>

但这些参数不是越小越好

例如:

windowSize 太大
    ↓
内存增加

windowSize 太小
    ↓
快速滚动可能白屏

initialNumToRender 太大
    ↓
首屏变慢

maxToRenderPerBatch 太大
    ↓
JS Thread 突然工作很多

需要根据页面实际情况调。


5. FlatList 最大的坑之一:renderItem

不要:

<FlatList
  data={data}
  renderItem={({ item }) => (
    <Item item={item} />
  )}
/>

在性能敏感场景下,可以:

const renderItem = useCallback(
  ({ item }) => <Item item={item} />,
  []
)

再配合:

const Item = React.memo(...)

但真正重要的不是“必须 useCallback”,而是:

让列表 Item 尽可能稳定,避免因为父组件更新导致大量 Item 重渲染。


6. getItemLayout

如果列表 Item 高度固定,非常值得用:

const ITEM_HEIGHT = 60

<FlatList
  data={data}
  getItemLayout={(_, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

这样 RN 不需要不断测量布局。

对于:

  • 聊天列表

  • 设置列表

  • 商品列表

  • 固定高度 Cell

特别有用。


7. 图片是性能杀手

例如服务器返回:

4000 × 4000

但手机只显示:

100 × 100

你却直接加载原图。

结果:

网络 ↑
内存 ↑
解码时间 ↑
GPU 压力 ↑

应该尽量:

服务端按照显示尺寸提供图片。

同时使用合适的缓存策略和图片组件。


8. 动画不要全部丢给 JS Thread

这是 RN 很重要的知识。

如果:

JS Thread
 ↓
每一帧计算
 ↓
Native UI

JS Thread 一忙:

JS 卡
 ↓
动画掉帧

所以动画最好尽量运行在 UI/Native 侧。

例如使用 Reanimated 的 worklet/UI-thread 模型,而不是大量依赖 JS 每帧更新。


9. 不要在 render 里做重计算

这是典型坑:

function Page({ data }) {
  const result = hugeCalculation(data)

  return <View />
}

每次 render:

state 更新
 ↓
render
 ↓
hugeCalculation()
 ↓
JS Thread 卡顿

可以:

const result = useMemo(
  () => hugeCalculation(data),
  [data]
)

或者更进一步:

把重计算移出渲染路径,必要时放到 native / worker / 后端处理。


10. State 管理也是性能问题

例如:

App
 └── Context
      └── 1000 个组件

Context 更新:

Context 更新
 ↓
大量 Consumer 更新
 ↓
大量 Render

常见解决方案:

  • 拆分 Context

  • 缩小 Provider 范围

  • selector

  • Zustand / Redux Toolkit 等状态管理

  • 避免把高频变化的数据放到全局 Context

特别是:

输入框
实时位置
动画状态
滚动位置

这种高频变化数据不要随便放全局状态。


11. 常见坑:useEffect

例如:

useEffect(() => {
  fetchData()
}, [data])

如果 fetchData() 又修改 data

data 改变
 ↓
useEffect
 ↓
fetch
 ↓
setData
 ↓
data 改变
 ↓
useEffect
 ↓
...

直接进入循环。

另外:

useEffect(() => {
  const timer = setInterval(...)
}, [])

却没有:

return () => clearInterval(timer)

就可能造成资源泄漏。


12. 内存泄漏

RN 常见来源:

setInterval
setTimeout
EventListener
WebSocket
Navigation listener
Native Event
Subscription

例如:

useEffect(() => {
  const subscription = eventEmitter.addListener(
    'event',
    handler
  )

  return () => {
    subscription.remove()
  }
}, [])

原则:

创建资源的地方负责销毁资源。


13. Navigation 常见性能坑

不要每次 render 都创建复杂对象:

<Stack.Screen
  options={{
    headerTitle: expensiveFunction()
  }}
/>

另外不要把大量复杂状态全部挂在 Navigation params:

navigation.navigate('Detail', {
  hugeObject
})

更合理:

navigation.navigate('Detail', {
  id
})

然后 Detail 根据 id 获取数据。


14. 首屏性能

用户感觉“RN 慢”,很多时候其实是:

JS Bundle 太大
+
启动时执行大量 JS
+
初始化大量 SDK
+
首屏请求太多

可以考虑:

  • Hermes

  • Bundle 拆分/懒加载策略

  • 延迟初始化非关键 SDK

  • 减少首屏 API 请求

  • 图片优化

  • 减少首屏组件数量

  • 避免启动阶段大量同步计算


15. 真正排查性能,不要靠猜

这是高级 RN 开发和普通 RN 开发的区别。

发现:

“页面很卡。”

不要马上:

useMemo!
useCallback!
React.memo!

应该先定位:

CPU?
 ↓
JS Thread?
 ↓
Native?
 ↓
GPU?
 ↓
内存?
 ↓
网络?

然后再优化。

常用工具包括:

  • React DevTools Profiler

  • Flipper(具体能力取决于 RN 版本/配置)

  • Android Studio Profiler

  • Xcode Instruments

  • Hermes profiling

  • Android GPU / Frame profiling


16. 面试可以直接这么总结

如果面试官问:

“React Native 怎么做性能优化?”

可以回答:

我一般从 JS、渲染、列表、图片、动画、Native 通信和启动性能几个方面排查。首先通过 Profiler 确认瓶颈,而不是盲目使用 memo。渲染层主要减少不必要的 re-render,合理使用 React.memo、useMemo 和 useCallback;列表使用 FlatList,并根据场景优化 windowSize、initialNumToRender、maxToRenderPerBatch 和 getItemLayout;图片控制分辨率和缓存;动画尽量放到 UI/Native 线程,减少 JS Thread 工作;对于高频状态避免放到全局 Context;同时注意 Effect、Timer、EventListener 等资源清理,避免内存泄漏。最后针对首屏减少 Bundle、初始化和网络请求。

这个回答已经比较接近中高级 RN 面试的水平。

如果你是准备 React Native 面试,我还建议重点准备一组更容易拉开差距的问题:JS Thread / UI Thread、Hermes、Fabric、JSI、TurboModule、Bridge 为什么慢、Reanimated 为什么快、FlatList 底层虚拟化、RN 新架构。这些比单纯背 useMemo 更重要。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐