react navite性能优化 & 常见坑

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

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

先建立一个简单模型:

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

常见问题基本分成:

  • JS Thread 卡顿
  • 渲染次数过多
  • 大列表性能差
  • 图片太大
  • Native ↔ JS 通信过多
  • 动画掉帧
  • 内存泄漏
  • 首屏加载慢
  • Bundle 太大

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

例如:

javascript 复制代码
const Item = ({ item }) => {
  return <Text>{item.name}</Text>
}

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

可以:

javascript 复制代码
const Item = React.memo(({ item }) => {
  return <Text>{item.name}</Text>
})

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

因为:

ini 复制代码
<Item
  data={{ name: 'Tom' }}
/>

每次 render 都创建新对象:

css 复制代码
第一次:{ name: 'Tom' } ← A
第二次:{ name: 'Tom' } ← B

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

可以:

scss 复制代码
const data = useMemo(
  () => ({ name: 'Tom' }),
  []
)

3. useCallback 也不要乱用

常见代码:

scss 复制代码
const handlePress = useCallback(() => {
  console.log(id)
}, [id])

如果子组件用了:

scss 复制代码
React.memo(...)

这种优化才比较有意义。

否则:

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

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

面试可以说:

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


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

这是非常高频的面试题。

不要:

ini 复制代码
<ScrollView>
  {data.map(item => (
    <Item item={item} />
  ))}
</ScrollView>

大量数据应该:

ini 复制代码
<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={item => String(item.id)}
/>

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


FlatList 常用优化

ini 复制代码
<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
  initialNumToRender={10}
  maxToRenderPerBatch={10}
  windowSize={5}
  removeClippedSubviews
/>

但这些参数不是越小越好

例如:

markdown 复制代码
windowSize 太大
    ↓
内存增加

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

initialNumToRender 太大
    ↓
首屏变慢

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

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


5. FlatList 最大的坑之一:renderItem

不要:

ini 复制代码
<FlatList
  data={data}
  renderItem={({ item }) => (
    <Item item={item} />
  )}
/>

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

javascript 复制代码
const renderItem = useCallback(
  ({ item }) => <Item item={item} />,
  []
)

再配合:

ini 复制代码
const Item = React.memo(...)

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

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


6. getItemLayout

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

ini 复制代码
const ITEM_HEIGHT = 60

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

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

对于:

  • 聊天列表
  • 设置列表
  • 商品列表
  • 固定高度 Cell

特别有用。


7. 图片是性能杀手

例如服务器返回:

yaml 复制代码
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 里做重计算

这是典型坑:

javascript 复制代码
function Page({ data }) {
  const result = hugeCalculation(data)

  return <View />
}

每次 render:

scss 复制代码
state 更新
 ↓
render
 ↓
hugeCalculation()
 ↓
JS Thread 卡顿

可以:

scss 复制代码
const result = useMemo(
  () => hugeCalculation(data),
  [data]
)

或者更进一步:

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


10. State 管理也是性能问题

例如:

markdown 复制代码
App
 └── Context
      └── 1000 个组件

Context 更新:

复制代码
Context 更新
 ↓
大量 Consumer 更新
 ↓
大量 Render

常见解决方案:

  • 拆分 Context
  • 缩小 Provider 范围
  • selector
  • Zustand / Redux Toolkit 等状态管理
  • 避免把高频变化的数据放到全局 Context

特别是:

复制代码
输入框
实时位置
动画状态
滚动位置

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


11. 常见坑:useEffect

例如:

scss 复制代码
useEffect(() => {
  fetchData()
}, [data])

如果 fetchData() 又修改 data

kotlin 复制代码
data 改变
 ↓
useEffect
 ↓
fetch
 ↓
setData
 ↓
data 改变
 ↓
useEffect
 ↓
...

直接进入循环。

另外:

scss 复制代码
useEffect(() => {
  const timer = setInterval(...)
}, [])

却没有:

javascript 复制代码
return () => clearInterval(timer)

就可能造成资源泄漏。


12. 内存泄漏

RN 常见来源:

javascript 复制代码
setInterval
setTimeout
EventListener
WebSocket
Navigation listener
Native Event
Subscription

例如:

scss 复制代码
useEffect(() => {
  const subscription = eventEmitter.addListener(
    'event',
    handler
  )

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

原则:

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


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

css 复制代码
<Stack.Screen
  options={{
    headerTitle: expensiveFunction()
  }}
/>

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

arduino 复制代码
navigation.navigate('Detail', {
  hugeObject
})

更合理:

bash 复制代码
navigation.navigate('Detail', {
  id
})

然后 Detail 根据 id 获取数据。


14. 首屏性能

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

diff 复制代码
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 更重要。

相关推荐
八角丶1 小时前
Node.js Cluster 详解
前端·node.js
名字还没想好☜1 小时前
React 用 useEffect 做轮询实战:setInterval 拿到旧 state 的闭包陷阱与正确清理
前端·javascript·react.js·react·useeffect
hiahiahia1231 小时前
AI Web 项目的文件到底应该怎么放?
前端·人工智能
愚公搬代码1 小时前
【愚公系列】《Web应用安全》003-测试环境的搭建
前端·安全
_codemonster2 小时前
主流前端技术分层选型
前端
犹豫的果冻布丁4 小时前
从零给 DeepSeek Harness 写一个壁纸皮肤插件(已开源)
前端·后端
漏刻有时4 小时前
数据可视化Three.js 3D 地图实战:单文件原生实现省域区县拉伸建模
前端
会说话的番茄4 小时前
AI 满嘴跑火车怎么办?给它配个"小抄"
前端·aigc
计算机魔术师4 小时前
AI 权力集中辩论实录:从「token 工厂」到「唯一幸存者」
前端