小白入门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 延伸阅读

相关推荐
Ruihong2 小时前
Vue 3 defineOptions 宏,用 VuReact 编译成 React 长什么样?
vue.js·react.js·面试
sealaugh322 小时前
react native(学习笔记第二课) 英语打卡微应用(1)-开始构建
笔记·学习·react native
Ruihong3 小时前
你的 Vue 3 defineEmits(),VuReact 会编译成什么样的 React?
vue.js·react.js·面试
im_AMBER3 小时前
学习 Redux Toolkit :从 Context 误区到 createSlice 实践
前端·javascript·学习·react.js·前端框架
Swift社区3 小时前
鸿蒙游戏的资源加载与管理
游戏·华为·harmonyos
前端不太难3 小时前
鸿蒙游戏如何避免“巨型页面文件”?
游戏·华为·harmonyos
千百元3 小时前
HBuilderX数据线运行mete80 (鸿蒙版本6.0.0)
华为·harmonyos
ZHENGZJM3 小时前
统一响应封装与 API 错误处理
react.js·go·gin