如果你说的是 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()
}
}, [])
原则:
创建资源的地方负责销毁资源。
13. Navigation 常见性能坑
不要每次 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 更重要。