react navite图片加载优化、大图卡顿、缓存策略

如果你是在做 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

不要请求:

yaml 复制代码
4000 × 4000

而应该请求类似:

复制代码
300 × 300

或者根据 DPR:

ini 复制代码
150 × 150 × 2 = 300 × 300

如果你的图片 CDN 支持参数,可以设计成:

ini 复制代码
/image/xxx.jpg?w=300&h=300&q=80

甚至:

ini 复制代码
/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:

ini 复制代码
import { Image } from 'expo-image';

<Image
  source={{ uri: imageUrl }}
  style={styles.image}
  contentFit="cover"
  cachePolicy="memory-disk"
/>

缓存策略:

ini 复制代码
cachePolicy="memory"

适合:

图片很容易重复出现,但不希望磁盘缓存太多。

ini 复制代码
cachePolicy="disk"

适合:

希望图片能够跨 App 重启继续使用缓存。

ini 复制代码
cachePolicy="memory-disk"

通常比较适合 Feed / 商品列表 / 社交 App。

还可以使用:

ini 复制代码
placeholder={blurhash}
transition={200}

改善用户感知上的加载体验。


4. 如果是纯 React Native

可以使用:

ini 复制代码
<Image
  source={{ uri: url }}
  style={styles.image}
/>

但需要注意:

RN Image 自身的缓存行为并不等于一个完整的图片缓存系统。

如果你对缓存有比较严格的需求,可以考虑:

  • expo-image
  • react-native-fast-image
  • 自己实现 HTTP/CDN 缓存策略

不过现在如果是新项目,我通常会优先考虑 expo-image,尤其是 Expo 项目。


5. FlatList 才是第二个大坑

很多时候用户说:

"图片加载卡顿"

实际上真正的问题是:

复制代码
FlatList
 ↓
同时创建大量 Image
 ↓
大量图片 decode
 ↓
JS / UI / Native 内存压力
 ↓
掉帧

例如:

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

可以进一步优化:

ini 复制代码
<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={(item) => item.id}
  initialNumToRender={8}
  maxToRenderPerBatch={6}
  windowSize={5}
  removeClippedSubviews
/>

但这些参数不要盲目调得很小

例如:

ini 复制代码
windowSize={2}

虽然内存下降,但快速滑动时可能频繁创建/销毁内容,反而出现闪烁和重新加载。


6. 图片组件一定要固定尺寸

不推荐:

css 复制代码
<Image
  source={{ uri: url }}
  style={{
    width: '100%',
    aspectRatio: 1,
  }}
/>

然后让布局不断计算。

更推荐让 FlatList item 的尺寸尽可能明确:

ini 复制代码
const IMAGE_SIZE = 120;

<Image
  source={{ uri: item.image }}
  style={{
    width: IMAGE_SIZE,
    height: IMAGE_SIZE,
  }}
/>

尤其是:

  • Grid
  • Avatar
  • 商品列表
  • 聊天列表
  • 瀑布流

明确尺寸可以减少 layout 和渲染的不确定性。


7. 缓存策略不要简单理解成"永久缓存"

比较合理的是:

markdown 复制代码
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 一定要稳定

缓存最怕这种情况:

ini 复制代码
https://cdn.xxx.com/a.jpg?t=123
https://cdn.xxx.com/a.jpg?t=456
https://cdn.xxx.com/a.jpg?t=789

虽然实际是同一张图片,但缓存系统可能认为是不同资源。

更好的方式:

ini 复制代码
https://cdn.xxx.com/image/a.jpg?v=abc123

内容发生变化的时候才修改版本:

ini 复制代码
a.jpg?v=abc124

这样:

vbnet 复制代码
URL
 ↓
Cache Key
 ↓
Image

能够稳定复用。


9. 大图不要在 RN 里面 resize

这是一个很重要的架构问题。

❌ 不推荐:

css 复制代码
服务器 5000×5000
       ↓
RN 下载
       ↓
RN decode
       ↓
RN resize
       ↓
显示 300×300

因为最昂贵的事情之一已经发生了:

yaml 复制代码
5000×5000 decode

应该:

css 复制代码
原图
 ↓
CDN Resize
 ↓
300×300
 ↓
RN download
 ↓
decode
 ↓
显示

这样能同时减少:

  • 网络流量
  • decode 时间
  • Bitmap 内存
  • GC 压力
  • UI 卡顿

10. 不要 preload 太多大图

例如:

ini 复制代码
images.forEach(image => {
  Image.prefetch(image.url);
});

如果 images 有 100 张:

复制代码
100 × 大图

可能导致:

  • 网络请求暴增
  • 磁盘缓存暴增
  • 内存压力
  • decode 峰值
  • 页面真正显示时反而卡

更合理的是:

diff 复制代码
当前屏幕
+
前后 1~2 屏

进行有限预加载。


11. 一个比较成熟的架构

如果是 Feed / 电商 / 社交 App,我会建议:

arduino 复制代码
             ┌──────────────┐
             │   Image CDN  │
             └──────┬───────┘
                    │
             width/height/q
                    │
                    ▼
             ┌──────────────┐
             │ expo-image   │
             └──────┬───────┘
                    │
          ┌─────────┴─────────┐
          ▼                   ▼
    Memory Cache          Disk Cache
          │                   │
          └─────────┬─────────┘
                    ▼
                FlatList
                    │
             Virtualization
                    │
                    ▼
               Visible Items

核心原则就是:

让"需要显示多少像素"决定"下载多少像素",而不是让原图尺寸决定一切。


12. 如果你的问题是"滚动时卡顿"

我会按这个优先级排查:

arduino 复制代码
① 图片是不是远大于显示尺寸
        ↓
② 是否使用了 CDN resize
        ↓
③ 图片 decode 是否占用大量内存
        ↓
④ FlatList 是否一次渲染太多 item
        ↓
⑤ Image 是否频繁 mount/unmount
        ↓
⑥ cache 是否命中
        ↓
⑦ 是否存在 JS thread 阻塞
        ↓
⑧ 是否存在 UI thread / GPU 压力

不要第一时间就调 windowSize / initialNumToRender

很多项目把 FlatList 参数调了一圈,最后发现真正的问题是:

yaml 复制代码
每个 100×100 的头像
实际上下载的是 3000×3000
相关推荐
小林ixn1 小时前
用 Next.js 和 Redis 撸一个 Markdown 笔记系统:RSC 实战与组件化拆解
前端·redis·next.js
渣波1 小时前
重构旅行体验:基于 React 的 AI 旅游助手对话系统实战解析
前端·javascript
今日无bug1 小时前
列表转树:一道题搞懂 HashMap 在算法里的价值
前端·数据结构
用户921080262861 小时前
5. 数据大屏实时通信第一步:为什么选择 WebSocket,以及如何接入 Socket.IO
前端
岁月留痕1681 小时前
17 实战项目二:实现“日记”项目多页面管理
前端
李顿波1 小时前
Chrome 插件弹窗一直停留在初始的小尺寸 —— 你看到的小方块
前端·javascript·chrome
悟空瞎说1 小时前
Cesium 与 Three.js 融合实战:在数字地球上渲染自定义 3D 场景
前端
渣波1 小时前
React 移动端首页架构实战:从并发请求到防御性编程的深度解析
前端·javascript
a1117761 小时前
原生 Markdown 阅读与编辑器 开源项目
前端·开源·软件