小白入门ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-fast-image

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

📌 开发环境声明:本文基于 React Native 0.72.90 版本进行开发适配


🚀 一、开篇引言

在移动应用开发中,图片加载是最常见也是最容易出现性能问题的地方。React Native 原生的 Image 组件虽然能满足基本需求,但在处理大量图片列表、内存缓存、磁盘缓存等方面存在明显短板。react-native-fast-image 正是为解决这些痛点而生的高性能图片加载组件,它底层使用 Glide(Android)、SDWebImage(iOS)以及鸿蒙原生图片加载能力,提供了完善的缓存机制和优先级加载功能。本文将带你深入了解如何在 HarmonyOS 平台上集成和使用这个强大的图片加载库。

1.1 图片加载的痛点与挑战

在实际开发中,我们经常会遇到以下问题:

问题场景 原生 Image 表现 FastImage 表现
长列表图片滚动 卡顿明显,内存飙升 流畅滑动,内存稳定
图片重复加载 每次都重新请求 自动缓存,秒开
内存管理 手动管理,易泄漏 自动清理,智能回收
加载优先级 无法控制 支持优先级队列
GIF 动图 支持有限 完美支持

📦 二、库概览

2.1 基本信息

项目 内容
库名称 react-native-fast-image
鸿蒙包名 @react-native-ohos/react-native-fast-image
官方仓库 https://github.com/DylanVann/react-native-fast-image
鸿蒙仓库 https://atomgit.com/openharmony-sig/rntpc_react-native-fast-image
开源协议 MIT

2.2 版本兼容性

三方库版本 RN 版本 发布地址
8.6.4 0.72 @react-native-ohos/react-native-fast-image
8.7.0 0.77 @react-native-ohos/react-native-fast-image

2.3 核心优势对比

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        功能对比                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   原生 Image                    FastImage                       │
│   ┌─────────────────┐          ┌─────────────────┐             │
│   │ ✓ 基础加载       │    →     │ ✓ 基础加载       │             │
│   │ ✓ 样式设置       │          │ ✓ 样式设置       │             │
│   │                 │          │ ✓ 内存缓存       │             │
│   │                 │          │ ✓ 磁盘缓存       │             │
│   │                 │          │ ✓ 优先级加载     │             │
│   │                 │          │ ✓ 预加载         │             │
│   │                 │          │ ✓ 缓存清理       │             │
│   └─────────────────┘          └─────────────────┘             │
│                                                                 │
│   功能子集 ────────────────────► 功能超集                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2.4 适用场景分析

场景 推荐指数 说明
图片列表/瀑布流 ⭐⭐⭐⭐⭐ 缓存机制大幅提升性能
头像加载 ⭐⭐⭐⭐⭐ 自动缓存,避免重复请求
轮播图/引导页 ⭐⭐⭐⭐⭐ 支持预加载,体验流畅
GIF 动图展示 ⭐⭐⭐⭐⭐ 完美支持动图播放
单张静态图片 ⭐⭐⭐ 可用原生 Image 替代

🔧 三、安装与配置

3.1 安装依赖(这里使用oh-tpl下的)

在项目根目录执行以下命令:

bash 复制代码
npm install @react-native-oh-tpl/react-native-fast-image@8.6.3-0.4.17-rc.1

或使用 yarn:

bash 复制代码
yarn add @react-native-ohos/react-native-fast-image

3.2 配置网络权限

由于 FastImage 需要加载网络图片,必须在 HarmonyOS 模块配置文件中声明网络权限。

打开 harmony/entry/src/main/module.json5,在 requestPermissions 数组中添加网络权限:

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

⚠️ 注意:如果不配置网络权限,加载网络图片时会静默失败,不会显示任何错误提示。

3.3 HarmonyOS 原生端配置

步骤一:配置 oh-package.json5

打开 harmony/oh-package.json5,添加 overrides 字段:

json 复制代码
{
  "overrides": {
    "@rnoh/react-native-openharmony": "./react_native_openharmony"
  }
}
步骤二:引入 har 包

打开 harmony/entry/oh-package.json5,添加依赖:

json 复制代码
"dependencies": {
  "@rnoh/react-native-openharmony": "file:../react_native_openharmony",
  "@react-native-ohos/react-native-fast-image": "file:../../node_modules/@react-native-ohos/react-native-fast-image/harmony/fast_image.har"
}
步骤三:配置 CMakeLists.txt

打开 harmony/entry/src/main/cpp/CMakeLists.txt,添加以下内容:

diff 复制代码
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-fast-image/src/main/cpp" ./fast-image)
# RNOH_END: manual_package_linking_1

add_library(rnoh_app SHARED
    ${GENERATED_CPP_FILES}
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)

# RNOH_BEGIN: manual_package_linking_2
+ target_link_libraries(rnoh_app PUBLIC rnoh_fast_image)
# RNOH_END: manual_package_linking_2
步骤四:配置 PackageProvider.cpp

打开 harmony/entry/src/main/cpp/PackageProvider.cpp,添加:

diff 复制代码
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
+ #include "FastImagePackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
        std::make_shared<RNOHGeneratedPackage>(ctx),
+       std::make_shared<FastImagePackage>(ctx),
    };
}
步骤五:配置 RNPackagesFactory.ts

打开 harmony/entry/src/main/ets/RNPackagesFactory.ts,添加:

diff 复制代码
+ import { FastImagePackage } from '@react-native-ohos/react-native-fast-image/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
+   new FastImagePackage(ctx)
  ];
}
步骤六:同步依赖

在 DevEco Studio 中点击右上角的 sync 按钮,或在终端执行:

bash 复制代码
cd harmony/entry
ohpm install

3.4 验证安装

安装完成后,检查 package.json 中是否包含依赖:

json 复制代码
{
  "dependencies": {
    "@react-native-ohos/react-native-fast-image": "^8.6.4"
  }
}

🎯 四、核心属性详解

4.1 图片源配置(source)

FastImage 支持多种图片源配置方式:

远程图片:

tsx 复制代码
<FastImage
  source={{
    uri: 'https://example.com/image.jpg',
    headers: { Authorization: 'Bearer token' },
  }}
/>

本地图片:

tsx 复制代码
<FastImage
  source={require('./local-image.png')}
/>
属性 类型 说明
uri string 远程图片地址
headers object 请求头(用于鉴权)
priority 'low'| 'normal' | 'high' 加载优先级(鸿蒙暂不支持)
cache 'immutable'| 'web' | 'cacheOnly' 缓存策略(鸿蒙暂不支持)

4.2 缩放模式(resizeMode)

FastImage 提供四种缩放模式,效果对比:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   contain          cover           stretch        center    │
│  ┌────────┐      ┌────────┐      ┌────────┐     ┌────────┐  │
│  │ ████   │      │████████│      │████████│     │        │  │
│  │ ████   │      │████████│      │████████│     │  ████  │  │
│  │        │      │████████│      │████████│     │  ████  │  │
│  └────────┘      └────────┘      └────────┘     └────────┘  │
│  保持比例        填充容器         拉伸填满       原始大小    │
│  可能留白        可能裁剪         可能变形       居中显示    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
tsx 复制代码
<FastImage
  source={{ uri: 'https://example.com/image.jpg' }}
  resizeMode={FastImage.resizeMode.cover}
/>

4.3 加载状态回调

FastImage 提供完整的加载生命周期回调:
Cache Network FastImage App Cache Network FastImage App alt [缓存命中] [缓存未命中] 设置 source 检查缓存 返回图片 onLoad onLoadStart 请求图片 数据传输 onProgress 加载完成 写入缓存 onLoad onLoadEnd

回调 参数 说明
onLoadStart - 开始加载
onProgress { nativeEvent: { loaded, total } } 加载进度
onLoad { nativeEvent: { width, height } } 加载成功
onError - 加载失败
onLoadEnd - 加载结束(成功或失败)

4.4 样式属性

属性 类型 说明
style ImageStyle 容器样式
tintColor string| number 图片着色(用于图标)
defaultSource number 加载占位图

tintColor 着色效果:

tsx 复制代码
<FastImage
  source={require('./icon.png')}
  tintColor="#007AFF"
  style={{ width: 24, height: 24 }}
/>

⚡ 五、静态方法详解

5.1 图片预加载(preload)

预加载可以在用户访问前提前下载图片,提升用户体验:

tsx 复制代码
FastImage.preload([
  { uri: 'https://example.com/image1.jpg' },
  { uri: 'https://example.com/image2.jpg' },
  { uri: 'https://example.com/image3.jpg' },
]);

适用场景:

  • 引导页图片预加载
  • 轮播图预加载
  • 详情页图片预加载

5.2 缓存清理

FastImage 提供两种缓存清理方式:

tsx 复制代码
// 清除内存缓存(快速,适合切换页面时调用)
await FastImage.clearMemoryCache();

// 清除磁盘缓存(较慢,适合设置页面调用)
await FastImage.clearDiskCache();

最佳实践:

tsx 复制代码
// 用户退出登录时清理缓存
const handleLogout = async () => {
  await FastImage.clearMemoryCache();
  await FastImage.clearDiskCache();
  // ... 其他清理逻辑
};

💡 六、完整实战示例

下面是一个完整的高性能图片列表示例,包含加载进度、错误处理、预加载等功能:

ts 复制代码
import React, { useEffect, useState, useCallback } from 'react';
import {
  View,
  Text,
  FlatList,
  StyleSheet,
  RefreshControl,
  ActivityIndicator,
  TouchableOpacity,
} from 'react-native';
import FastImage, { OnLoadEvent, OnProgressEvent } from 'react-native-fast-image';

interface ImageItem {
  id: string;
  url: string;
  title: string;
}

const IMAGE_LIST: ImageItem[] = [
  { id: '1', url: 'https://picsum.photos/400/300?random=1', title: '风景图片一' },
  { id: '2', url: 'https://picsum.photos/400/300?random=2', title: '风景图片二' },
  { id: '3', url: 'https://picsum.photos/400/300?random=3', title: '风景图片三' },
  { id: '4', url: 'https://picsum.photos/400/300?random=4', title: '风景图片四' },
  { id: '5', url: 'https://picsum.photos/400/300?random=5', title: '风景图片五' },
  { id: '6', url: 'https://picsum.photos/400/300?random=6', title: '风景图片六' },
];

interface ImageState {
  loading: boolean;
  progress: number;
  error: boolean;
}

const FastImageListDemo: React.FC = () => {
  const [refreshing, setRefreshing] = useState(false);
  const [imageStates, setImageStates] = useState<Record<string, ImageState>>({});

  useEffect(() => {
    FastImage.preload(IMAGE_LIST.map(item => ({ uri: item.url })));
  }, []);

  const handleLoadStart = useCallback((id: string) => {
    setImageStates(prev => ({
      ...prev,
      [id]: { loading: true, progress: 0, error: false },
    }));
  }, []);

  const handleProgress = useCallback((id: string, event: OnProgressEvent) => {
    const { loaded, total } = event.nativeEvent;
    const progress = total > 0 ? Math.round((loaded / total) * 100) : 0;
    setImageStates(prev => ({
      ...prev,
      [id]: { ...prev[id], progress, loading: true },
    }));
  }, []);

  const handleLoad = useCallback((id: string, event: OnLoadEvent) => {
    const { width, height } = event.nativeEvent;
    console.log(`图片 ${id} 加载成功: ${width}x${height}`);
    setImageStates(prev => ({
      ...prev,
      [id]: { loading: false, progress: 100, error: false },
    }));
  }, []);

  const handleError = useCallback((id: string) => {
    console.log(`图片 ${id} 加载失败`);
    setImageStates(prev => ({
      ...prev,
      [id]: { loading: false, progress: 0, error: true },
    }));
  }, []);

  const handleRefresh = useCallback(() => {
    setRefreshing(true);
    FastImage.clearMemoryCache()
      .then(() => {
        setImageStates({});
      })
      .finally(() => {
        setRefreshing(false);
      });
  }, []);

  const renderImageItem = ({ item }: { item: ImageItem }) => {
    const state = imageStates[item.id] || { loading: false, progress: 100, error: false };

    return (
      <View style={styles.imageCard}>
        <View style={styles.imageContainer}>
          <FastImage
            source={{ uri: item.url }}
            style={styles.image}
            resizeMode={FastImage.resizeMode.cover}
            onLoadStart={() => handleLoadStart(item.id)}
            onProgress={(e) => handleProgress(item.id, e)}
            onLoad={(e) => handleLoad(item.id, e)}
            onError={() => handleError(item.id)}
          />
          {state.loading && (
            <View style={styles.overlay}>
              <ActivityIndicator size="large" color="#007AFF" />
              <Text style={styles.progressText}>{state.progress}%</Text>
            </View>
          )}
          {state.error && (
            <View style={styles.overlay}>
              <Text style={styles.errorText}>加载失败</Text>
              <TouchableOpacity style={styles.retryButton}>
                <Text style={styles.retryText}>点击重试</Text>
              </TouchableOpacity>
            </View>
          )}
        </View>
        <Text style={styles.title}>{item.title}</Text>
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>FastImage 图片列表</Text>
        <TouchableOpacity onPress={handleRefresh}>
          <Text style={styles.clearButton}>清理缓存</Text>
        </TouchableOpacity>
      </View>
      <FlatList
        data={IMAGE_LIST}
        keyExtractor={(item) => item.id}
        renderItem={renderImageItem}
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
        }
        contentContainerStyle={styles.listContent}
        showsVerticalScrollIndicator={false}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#FFFFFF',
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  headerTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333333',
  },
  clearButton: {
    fontSize: 14,
    color: '#007AFF',
  },
  listContent: {
    padding: 12,
  },
  imageCard: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    marginBottom: 12,
    overflow: 'hidden',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  imageContainer: {
    position: 'relative',
  },
  image: {
    width: '100%',
    height: 200,
    backgroundColor: '#F0F0F0',
  },
  overlay: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(255, 255, 255, 0.9)',
  },
  progressText: {
    marginTop: 8,
    fontSize: 14,
    color: '#007AFF',
    fontWeight: '500',
  },
  errorText: {
    fontSize: 16,
    color: '#FF3B30',
    marginBottom: 12,
  },
  retryButton: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    backgroundColor: '#007AFF',
    borderRadius: 8,
  },
  retryText: {
    fontSize: 14,
    color: '#FFFFFF',
  },
  title: {
    padding: 12,
    fontSize: 16,
    fontWeight: '500',
    color: '#333333',
  },
});

export default FastImageListDemo;

⚠️ 七、注意事项与常见问题

7.1 HarmonyOS 平台限制

属性/方法 支持情况 说明
source.priority ❌ 不支持 加载优先级暂未实现
source.cache ❌ 不支持 缓存策略暂未实现
其他属性 ✅ 支持 功能完整

7.2 常见问题 FAQ

Q1: 图片不显示怎么办?

检查以下几点:

  • 确认图片 URL 是否正确
  • 检查网络权限是否配置
  • 查看 onError 回调是否有错误信息
  • 尝试清除缓存后重新加载

Q2: 列表滑动卡顿如何优化?

tsx 复制代码
// 1. 使用 getItemLayout 优化
<FlatList
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

// 2. 设置合适的 initialNumToRender
<FlatList initialNumToRender={5} />

// 3. 使用 keyExtractor 提供稳定的 key
<FlatList keyExtractor={(item) => item.id} />

Q3: 如何处理 HTTPS 证书问题?

在开发环境可以临时忽略证书验证,生产环境需要正确配置证书:

tsx 复制代码
// 仅用于开发调试
source={{
  uri: 'https://example.com/image.jpg',
  headers: { 'Accept-Encoding': 'gzip' },
}}

7.3 性能优化建议

优化项 建议
图片尺寸 根据显示尺寸选择合适分辨率,避免加载过大图片
列表优化 使用 getItemLayout、initialNumToRender 等属性
缓存策略 合理使用 preload 预加载关键图片
内存管理 页面卸载时调用 clearMemoryCache
占位图 设置 defaultSource 提升用户体验

📊 八、总结

8.1 核心要点回顾

  • FastImage 底层使用原生图片加载库,性能远超原生 Image
  • 支持内存缓存和磁盘缓存,大幅减少网络请求
  • 提供完整的加载生命周期回调,便于状态管理
  • 预加载功能可提前下载图片,提升用户体验

8.2 延伸阅读

相关推荐
AlbertZein11 小时前
ImageKnifePro 源码解读:鸿蒙图片加载框架全貌
harmonyos
AlbertZein12 小时前
鸿蒙工程化:build-profile.json5 逐字段解析
harmonyos
暗不需求14 小时前
# 深入 React Todos:从零实现一个状态提升与本地持久化的待办应用
javascript·react.js·全栈
前端技术15 小时前
鸿蒙ArkTS 自定义底部导航栏(Tabs+@Builder 极简实现)
harmonyos·鸿蒙
骑自行车的码农15 小时前
数据的源头 —— JSX
react.js
Swift社区15 小时前
为什么“页面跳转”在鸿蒙 PC 上是错误设计?
华为·harmonyos
时光足迹16 小时前
Tiptap 简单编辑器模版
前端·javascript·react.js
时光足迹16 小时前
Tiptap编辑器
前端·javascript·react.js
时光足迹16 小时前
电子书阅读器之笔记高亮(跨段处理)
前端·javascript·react.js
空中海18 小时前
03 渲染机制、性能优化与现代 React
javascript·react.js·性能优化