如果你是在做React Native 图片加载优化,尤其是「大图卡顿 + 列表图片 + 缓存」,核心不是单纯换一个 Image 组件,而是要同时处理:
图片尺寸 → 解码 → 内存 → 磁盘缓存 → 列表复用 → 网络加载
1. 大图卡顿的核心原因
比如服务器返回一张4000 × 3000的 JPEG:
文件可能只有 2 MB
但解码到内存后大约是:
4000 × 3000 × 4 ≈ 48 MB
如果一个 FlatList 同时出现 5 张这样的图片,就可能瞬间产生200+ MB的 Bitmap/RGBA 内存压力。
所以:
不要把“压缩文件大小”和“降低图片内存占用”混为一谈。
最有效的优化通常是让服务端根据显示尺寸返回合适分辨率。
2. 首选:服务端图片缩放
假设 UI 中图片实际只显示:
屏幕:375px 图片:150 × 150不要请求:
4000 × 4000而应该请求类似:
300 × 300或者根据 DPR:
150 × 150 × 2 = 300 × 300如果你的图片 CDN 支持参数,可以设计成:
/image/xxx.jpg?w=300&h=300&q=80甚至:
/image/xxx.jpg?w=600&h=600&dpr=2推荐尺寸策略
| UI 图片尺寸 | 推荐下载尺寸 |
|---|---|
| 50×50 | 100×100 |
| 100×100 | 200×200 |
| 150×150 | 300×300 |
| 300×200 | 600×400 |
| 全屏图片 | 根据设备实际分辨率 |
这通常是 RN 图片性能优化里收益最大的一步。
3. React Native 中不要只用<Image>
如果是大量图片、Feed、聊天列表、瀑布流,我更推荐考虑:
expo-image
如果你使用 Expo:
import { Image } from 'expo-image'; <Image source={{ uri: imageUrl }} style={styles.image} contentFit="cover" cachePolicy="memory-disk" />缓存策略:
cachePolicy="memory"适合:
图片很容易重复出现,但不希望磁盘缓存太多。
cachePolicy="disk"适合:
希望图片能够跨 App 重启继续使用缓存。
cachePolicy="memory-disk"通常比较适合 Feed / 商品列表 / 社交 App。
还可以使用:
placeholder={blurhash} transition={200}改善用户感知上的加载体验。
4. 如果是纯 React Native
可以使用:
<Image source={{ uri: url }} style={styles.image} />但需要注意:
RN Image 自身的缓存行为并不等于一个完整的图片缓存系统。
如果你对缓存有比较严格的需求,可以考虑:
expo-imagereact-native-fast-image自己实现 HTTP/CDN 缓存策略
不过现在如果是新项目,我通常会优先考虑expo-image,尤其是 Expo 项目。
5. FlatList 才是第二个大坑
很多时候用户说:
“图片加载卡顿”
实际上真正的问题是:
FlatList ↓ 同时创建大量 Image ↓ 大量图片 decode ↓ JS / UI / Native 内存压力 ↓ 掉帧例如:
<FlatList data={data} renderItem={renderItem} keyExtractor={(item) => item.id} />可以进一步优化:
<FlatList data={data} renderItem={renderItem} keyExtractor={(item) => item.id} initialNumToRender={8} maxToRenderPerBatch={6} windowSize={5} removeClippedSubviews />但这些参数不要盲目调得很小。
例如:
windowSize={2}虽然内存下降,但快速滑动时可能频繁创建/销毁内容,反而出现闪烁和重新加载。
6. 图片组件一定要固定尺寸
不推荐:
<Image source={{ uri: url }} style={{ width: '100%', aspectRatio: 1, }} />然后让布局不断计算。
更推荐让 FlatList item 的尺寸尽可能明确:
const IMAGE_SIZE = 120; <Image source={{ uri: item.image }} style={{ width: IMAGE_SIZE, height: IMAGE_SIZE, }} />尤其是:
Grid
Avatar
商品列表
聊天列表
瀑布流
明确尺寸可以减少 layout 和渲染的不确定性。
7. 缓存策略不要简单理解成“永久缓存”
比较合理的是:
Memory Cache ↓ Disk Cache ↓ Network例如:
第一次打开 Network ↓ Disk Cache ↓ Memory Cache 第二次进入页面 Memory Cache ↓ 直接显示 App 重启 Disk Cache ↓ 读取 缓存不存在 / 过期 Network对于头像、商品图片、Feed 图片,可以采用不同策略。
Avatar
Memory + Disk因为重复率很高。
Feed 图片
Memory + Disk但要限制磁盘缓存规模。
一次性大图
Disk甚至:
不长期缓存8. URL 一定要稳定
缓存最怕这种情况:
https://cdn.xxx.com/a.jpg?t=123 https://cdn.xxx.com/a.jpg?t=456 https://cdn.xxx.com/a.jpg?t=789虽然实际是同一张图片,但缓存系统可能认为是不同资源。
更好的方式:
https://cdn.xxx.com/image/a.jpg?v=abc123内容发生变化的时候才修改版本:
a.jpg?v=abc124这样:
URL ↓ Cache Key ↓ Image能够稳定复用。
9. 大图不要在 RN 里面 resize
这是一个很重要的架构问题。
❌ 不推荐:
服务器 5000×5000 ↓ RN 下载 ↓ RN decode ↓ RN resize ↓ 显示 300×300因为最昂贵的事情之一已经发生了:
5000×5000 decode应该:
原图 ↓ CDN Resize ↓ 300×300 ↓ RN download ↓ decode ↓ 显示这样能同时减少:
网络流量
decode 时间
Bitmap 内存
GC 压力
UI 卡顿
10. 不要 preload 太多大图
例如:
images.forEach(image => { Image.prefetch(image.url); });如果images有 100 张:
100 × 大图可能导致:
网络请求暴增
磁盘缓存暴增
内存压力
decode 峰值
页面真正显示时反而卡
更合理的是:
当前屏幕 + 前后 1~2 屏进行有限预加载。
11. 一个比较成熟的架构
如果是 Feed / 电商 / 社交 App,我会建议:
┌──────────────┐ │ Image CDN │ └──────┬───────┘ │ width/height/q │ ▼ ┌──────────────┐ │ expo-image │ └──────┬───────┘ │ ┌─────────┴─────────┐ ▼ ▼ Memory Cache Disk Cache │ │ └─────────┬─────────┘ ▼ FlatList │ Virtualization │ ▼ Visible Items核心原则就是:
让“需要显示多少像素”决定“下载多少像素”,而不是让原图尺寸决定一切。
12. 如果你的问题是“滚动时卡顿”
我会按这个优先级排查:
① 图片是不是远大于显示尺寸 ↓ ② 是否使用了 CDN resize ↓ ③ 图片 decode 是否占用大量内存 ↓ ④ FlatList 是否一次渲染太多 item ↓ ⑤ Image 是否频繁 mount/unmount ↓ ⑥ cache 是否命中 ↓ ⑦ 是否存在 JS thread 阻塞 ↓ ⑧ 是否存在 UI thread / GPU 压力不要第一时间就调windowSize/initialNumToRender。
很多项目把 FlatList 参数调了一圈,最后发现真正的问题是:
每个 100×100 的头像 实际上下载的是 3000×3000