HarmonyOS APP开发---"图迹"旅行相册App,需要用到这个库
做一个旅行相册 App,用户上传的几百张旅途照片要瀑布流展示,点击大图要秒开。RN 自带 Image 没缓存、每次重新下载------
@react-native-ohos/react-native-fast-image的二级缓存 + 预加载,让相册体验追平原生。
📦 仓库地址:https://gitcode.com/CPF-RN/rntpc_react-native-fast-image | 安装:npm install @react-native-ohos/react-native-fast-image
写在前面
"图迹"的核心体验是"滑到就看到"------用户打开某次旅行的相册,几十张照片瀑布流排列,滑到哪里哪里就秒出图。点开大图,几乎无延迟地全屏展示。
这对图片加载的要求极高:
- 缓存是刚需:滑过的图再滑回来不能重新下载,否则流量爆炸 + 体验卡顿
- 大图预加载:用户看缩略图时,大图已经在后台拉好了,点开即出
- 优先级:当前可见区域的图优先加载,不可见区域降级
RN 自带 Image 组件在鸿蒙上没有原生磁盘缓存,每次都要重新下载。react-native-fast-image 底层适配了 HarmonyOS 原生缓存引擎------内存缓存 + 磁盘缓存 + 优先级队列 + 预加载。
这篇文章聊什么
- 瀑布流加载------FastImage + FlatList 瀑布流
- 大图预加载------缩略图加载时预加载高清大图
- 优先级控制------可见区域优先、不可见区域降级
#mermaid-svg-TVH5moKzuaMudmST{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TVH5moKzuaMudmST .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TVH5moKzuaMudmST .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TVH5moKzuaMudmST .error-icon{fill:#552222;}#mermaid-svg-TVH5moKzuaMudmST .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TVH5moKzuaMudmST .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TVH5moKzuaMudmST .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TVH5moKzuaMudmST .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TVH5moKzuaMudmST .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TVH5moKzuaMudmST .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TVH5moKzuaMudmST .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TVH5moKzuaMudmST .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TVH5moKzuaMudmST .marker.cross{stroke:#333333;}#mermaid-svg-TVH5moKzuaMudmST svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TVH5moKzuaMudmST p{margin:0;}#mermaid-svg-TVH5moKzuaMudmST .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TVH5moKzuaMudmST .cluster-label text{fill:#333;}#mermaid-svg-TVH5moKzuaMudmST .cluster-label span{color:#333;}#mermaid-svg-TVH5moKzuaMudmST .cluster-label span p{background-color:transparent;}#mermaid-svg-TVH5moKzuaMudmST .label text,#mermaid-svg-TVH5moKzuaMudmST span{fill:#333;color:#333;}#mermaid-svg-TVH5moKzuaMudmST .node rect,#mermaid-svg-TVH5moKzuaMudmST .node circle,#mermaid-svg-TVH5moKzuaMudmST .node ellipse,#mermaid-svg-TVH5moKzuaMudmST .node polygon,#mermaid-svg-TVH5moKzuaMudmST .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TVH5moKzuaMudmST .rough-node .label text,#mermaid-svg-TVH5moKzuaMudmST .node .label text,#mermaid-svg-TVH5moKzuaMudmST .image-shape .label,#mermaid-svg-TVH5moKzuaMudmST .icon-shape .label{text-anchor:middle;}#mermaid-svg-TVH5moKzuaMudmST .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TVH5moKzuaMudmST .rough-node .label,#mermaid-svg-TVH5moKzuaMudmST .node .label,#mermaid-svg-TVH5moKzuaMudmST .image-shape .label,#mermaid-svg-TVH5moKzuaMudmST .icon-shape .label{text-align:center;}#mermaid-svg-TVH5moKzuaMudmST .node.clickable{cursor:pointer;}#mermaid-svg-TVH5moKzuaMudmST .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TVH5moKzuaMudmST .arrowheadPath{fill:#333333;}#mermaid-svg-TVH5moKzuaMudmST .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TVH5moKzuaMudmST .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TVH5moKzuaMudmST .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TVH5moKzuaMudmST .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TVH5moKzuaMudmST .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TVH5moKzuaMudmST .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TVH5moKzuaMudmST .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TVH5moKzuaMudmST .cluster text{fill:#333;}#mermaid-svg-TVH5moKzuaMudmST .cluster span{color:#333;}#mermaid-svg-TVH5moKzuaMudmST div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TVH5moKzuaMudmST .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TVH5moKzuaMudmST rect.text{fill:none;stroke-width:0;}#mermaid-svg-TVH5moKzuaMudmST .icon-shape,#mermaid-svg-TVH5moKzuaMudmST .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TVH5moKzuaMudmST .icon-shape p,#mermaid-svg-TVH5moKzuaMudmST .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TVH5moKzuaMudmST .icon-shape .label rect,#mermaid-svg-TVH5moKzuaMudmST .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TVH5moKzuaMudmST .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TVH5moKzuaMudmST .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TVH5moKzuaMudmST :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
打开旅行相册
加载缩略图列表
缓存命中?
秒出图
网络下载
存入磁盘缓存
存入内存缓存
预加载相邻大图
用户点击
大图缓存命中?
秒开大图
快速下载
第一步:安装
bash
npm install @react-native-ohos/react-native-fast-image
第二步:瀑布流照片列表
tsx
import FastImage from '@react-native-ohos/react-native-fast-image'
import { FlatList, View, Text, Dimensions } from 'react-native'
const { width } = Dimensions.get('window')
const COLUMN_WIDTH = (width - 24) / 2 // 两列瀑布流
interface Photo {
id: string
thumb: string // 缩略图 URL
full: string // 高清大图 URL
title: string
}
function PhotoCard({ photo }: { photo: Photo }) {
return (
<View style={{ width: COLUMN_WIDTH, marginBottom: 12 }}>
<FastImage
style={{ width: '100%', height: COLUMN_WIDTH * 1.3, borderRadius: 8 }}
source={{
uri: photo.thumb,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable, // 永不重复下载
}}
resizeMode={FastImage.resizeMode.cover}
/>
<Text style={{ fontSize: 13, color: '#666', marginTop: 4 }}>{photo.title}</Text>
</View>
)
}
// 瀑布流列表
<FlatList
data={photos}
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <PhotoCard photo={item} />}
contentContainerStyle={{ padding: 12 }}
/>
cache: FastImage.cacheControl.immutable 是关键------图片 URL 不变就永不重新请求,旅行照片一旦上传就不会变,完美匹配这个策略。
第三步:大图预加载
用户在看缩略图列表时,提前把当前可见区域相邻的几张高清大图拉进缓存:
tsx
import { useEffect, useRef } from 'react'
function usePreloadFullImages(photos: Photo[], visibleStart: number, visibleEnd: number) {
useEffect(() => {
// 预加载当前可见区域 + 前后各 2 张的大图
const toPreload: Array<{ uri: string; priority: any }> = []
for (let i = Math.max(0, visibleStart - 2); i <= Math.min(photos.length - 1, visibleEnd + 2); i++) {
toPreload.push({
uri: photos[i].full,
priority: FastImage.priority.low, // 预加载用低优先级,不抢当前图
})
}
FastImage.preload(toPreload)
}, [visibleStart, visibleEnd])
}
预加载用 priority.low------不抢当前可见缩略图的带宽,等缩略图都加载完了再拉大图。
第四步:点击查看大图(秒开)
tsx
function FullScreenViewer({ photo, onClose }: { photo: Photo; onClose: () => void }) {
return (
<View style={{ flex: 1, backgroundColor: '#000' }}>
<FastImage
style={{ flex: 1 }}
source={{
uri: photo.full,
priority: FastImage.priority.high, // 大图用高优先级
cache: FastImage.cacheControl.immutable,
}}
resizeMode={FastImage.resizeMode.contain}
onLoad={(e) => {
console.log(`大图加载完成: ${e.nativeEvent.width}x${e.nativeEvent.height}`)
}}
/>
<TouchableOpacity onPress={onClose} style={{ position: 'absolute', top: 40, right: 20 }}>
<Text style={{ color: '#fff', fontSize: 18 }}>✕</Text>
</TouchableOpacity>
</View>
)
}
因为第三步已经预加载了大图,用户点击时缓存已命中,几乎零延迟秒开。
为什么"图迹"选了 FastImage?
| 需求 | RN Image | @react-native-ohos/react-native-fast-image |
|---|---|---|
| 磁盘缓存 | ❌ | ✅ 二级缓存 |
| 预加载 | ❌ | ✅ preload() |
| 优先级 | ❌ | ✅ low/normal/high |
| 缓存策略 | ❌ | ✅ immutable/web |
| 大图秒开 | ❌ 重新下载 | ✅ 预加载命中缓存 |
| 进度回调 | ❌ | ✅ onProgress |
总结
"图迹"这个场景里,@react-native-ohos/react-native-fast-image 解决了三件事:
- 缓存不重下 ------
immutable策略 + 二级缓存,滑过的图再滑回来秒出 - 大图秒开------缩略图加载时低优先级预加载大图,点击即出
- 优先级调度------可见区域 normal,预加载 low,大图查看 high,带宽合理分配
如果你也在做相册、电商、社交等大量图片的 RN 鸿蒙应用,react-native-fast-image 是性能体验的底线保障。