ReactNative 鸿蒙跨端行业统称RNOH(React Native for OpenHarmony),是社区主导、华为协同共建的适配兼容层,核心是给原生 React Native 框架补上鸿蒙平台的底层实现,让一套 React/TypeScript 业务代码,无需大规模改写、不用学习 ArkTS 原生语法,就能同时编译运行在安卓、iOS、鸿蒙手机、平板、智慧屏、PC 全场景设备,最终打包为鸿蒙标准 HAP 安装包直接上架华为应用市场,它属于原生控件渲染型跨端,和 Uniapp、Cordova 这类 WebView 网页套壳跨端有着本质区别,页面渲染、手势交互、硬件调用全部依托鸿蒙 ArkUI 原生图形引擎完成,不存在浏览器内核带来的性能损耗。

底层运行架构原理

整套架构基于 React Native新架构(Fabric 渲染器 + JSI 直通通信 + TurboModule 按需模块)搭建,摒弃旧版 RN 异步 JSON 桥接的低效模式,分为三层执行链路。最上层是开发者编写的 React 业务代码,通过 Metro 打包后依托 Hermes JS 引擎执行逻辑、生成虚拟 DOM 节点;中间层是 RNOH 核心适配层,依靠 JSI 共享内存直接打通 JS 层与 C++ 原生层,用 Yoga 引擎统一完成 Flex 布局计算,Fabric 调度渲染任务,TurboModule 实现系统能力懒加载,避免 App 启动全量初始化拖慢开机速度;最底层直接通过鸿蒙 ArkUI C-API 对接系统原生组件,RN 里的 View、Text、ScrollView、Image、点击组件会一一映射为鸿蒙 Div、Text、Scroll、Image、Button 原生控件,弹窗、状态栏、权限、传感器、文件存储等系统调用,同样由适配层转发给鸿蒙系统 NAPI 接口实现调用,同时额外封装了鸿蒙分布式软总线、原子化服务、折叠屏自适应、多设备流转等独有系统接口,供 RN 业务直接调用。

开发与工程使用形态

前端开发者维持原有 RN 开发习惯,使用 React Hooks、React Navigation 路由、Redux/Zustand 状态管理、Axios 网络请求等成熟前端工具链,业务代码里仅需少量平台判断处理差异化逻辑,绝大部分业务代码和安卓 iOS 两端完全复用。工程构建上,在原有 RN 项目中接入 RNOH 鸿蒙工程模板,通过鸿蒙 DevEco 工具链编译,直接产出 HAP/HSP 鸿蒙安装包,支持混合原生开发,遇到 RN 生态缺失的硬件、底层系统能力时,可以直接用 ArkTS/C++ 编写鸿蒙原生 TurboModule 模块,导出 JS 接口给 React 业务调用,补齐能力短板。

随着鸿蒙生态的持续完善,跨端开发需求日益迫切,React Native(以下简称RN)作为成熟的跨端框架,凭借“一次编写、多端运行”的特性,成为衔接React生态与鸿蒙平台的重要桥梁。本文将以“应用流量排行榜”RN案例为载体,深度解读其技术实现细节、跨端适配思路,以及如何打包适配鸿蒙OpenHarmony平台,为RN开发者转向鸿蒙跨端开发提供可落地的参考。

本次解析的案例是一个功能完整的应用流量排行榜,具备流量数据展示、时间范围筛选、排序切换、应用详情呈现等核心功能,完全基于RN原生语法开发,最终可通过打包适配鸿蒙OpenHarmony设备,全程无需修改核心业务逻辑,充分体现RN跨端开发的高效性与兼容性。

一、案例整体架构

首先明确核心定位:该案例并非单纯的RN前端页面,而是面向鸿蒙跨端的可复用工程,其架构设计遵循“业务与适配分离、原生能力复用”的原则,既保留RN的开发效率,又兼顾鸿蒙平台的运行特性。

整体架构分为三层:核心业务层(数据处理、UI渲染)、跨端适配层(组件适配、API兼容)、平台部署层(打包配置、鸿蒙工程集成)。其中核心业务层完全复用RN原生语法,跨端适配层依托RN的跨端特性与鸿蒙的兼容性支持,实现“一套代码”在RN调试环境与鸿蒙设备中均可正常运行,平台部署层则通过专属打包命令,完成RN代码到鸿蒙可运行文件的转换。

代码开篇的依赖引入的设计,就充分考虑了跨端兼容性——仅引入React Native核心组件(SafeAreaView、View、Text等)和基础API(useState、Dimensions等),未使用任何平台专属依赖,这是实现跨端复用的前提。同时,引入的Base64图标库,避免了不同平台图标格式不兼容的问题,确保在RN调试环境与鸿蒙设备中,图标展示效果一致。

2.1 类型

案例中通过TypeScript定义了AppUsage数据类型,明确了应用流量数据的各项字段(id、name、packageName、mobileData等),这种强类型约束不仅提升了代码的可维护性,更避免了跨端运行时因数据类型不匹配导致的异常——鸿蒙平台对数据类型的校验更为严格,TypeScript的类型定义可提前规避此类问题。

状态管理采用React Native原生的useState钩子,定义了timeRange(时间范围)和sortBy(排序方式)两个核心状态,分别控制时间筛选和流量排序逻辑。状态更新逻辑(setTimeRange、setSortBy)完全复用RN语法,无需针对鸿蒙平台做额外修改,这是因为RN的状态管理机制与鸿蒙的组件状态管理逻辑高度兼容,鸿蒙通过适配层,可直接识别并解析RN的状态更新操作。

2.2 数据处理

流量数据的处理的核心是排序逻辑和格式化逻辑,两者均考虑了跨端显示的一致性。排序逻辑通过数组sort方法,根据sortBy的不同取值(total、mobile、wifi),实现总流量、移动数据、WiFi数据的排序,排序算法简洁高效,且不依赖任何平台专属API,可直接在鸿蒙平台运行。

formatDataSize函数实现了流量单位的格式化(MB转GB),通过判断流量大小是否超过1024MB,自动转换为GB或MB单位,并保留1位小数。这种格式化逻辑贴合移动端(包括鸿蒙设备)的显示习惯,避免了因单位不统一导致的用户体验差异。值得注意的是,函数中的数值计算采用原生JavaScript语法,鸿蒙平台可完全兼容,无需额外适配。

getDataColor函数根据流量使用占比,返回不同的颜色值(红色、橙色、绿色),用于进度条的颜色渲染。这里选用的颜色值均为标准RGB颜色,避免了使用平台专属的颜色常量,确保在RN调试环境与鸿蒙设备中,进度条颜色显示一致。同时,颜色判断逻辑(占比>80%为红色、>50%为橙色、否则为绿色),贴合用户对流量使用状态的认知,兼顾实用性与跨端一致性。

2.3 UI

UI渲染是跨端开发的核心难点,案例中通过RN的Flex布局和组件封装,实现了UI在不同平台的自适应显示,同时适配鸿蒙设备的屏幕特性。

首先,通过Dimensions.get(‘window’)获取设备的宽高(width、height),用于后续组件的自适应布局——鸿蒙设备的屏幕尺寸多样,这种动态获取屏幕宽高的方式,可确保组件在不同尺寸的鸿蒙设备中,布局比例一致。例如,overviewCard组件采用flexDirection: 'row’和justifyContent: 'space-around’的布局,实现三个流量总览项的均匀分布,无论屏幕宽高如何变化,均能保持一致的显示效果。

其次,组件的封装的设计遵循“原子化”原则,将应用项(renderAppItem)、筛选选项(filterOption)等可复用组件单独封装,提升代码复用性的同时,也便于跨端适配。以renderAppItem组件为例,该组件封装了应用排行的完整UI(排名、图标、应用信息、流量数据、进度条),通过flex布局实现各元素的对齐与分布,其中进度条的宽度通过动态计算(Math.min(100, percentage)%),确保进度条不会超出容器范围,这种设计既适配RN的渲染逻辑,也符合鸿蒙平台的UI渲染规范。

另外,组件样式的定义采用React Native的StyleSheet.create方法,这种样式定义方式与鸿蒙的样式定义逻辑兼容——鸿蒙通过适配层,可将RN的StyleSheet样式,转换为自身的样式格式。同时,样式中避免使用平台专属的样式属性(如RN的shadow相关属性,鸿蒙可通过适配层识别并转换为自身的阴影效果),确保UI样式在跨端环境中一致。

2.4 交互逻辑

案例中的交互逻辑(筛选切换、设置点击、导航点击),均采用React Native原生的TouchableOpacity组件和onPress事件,无需针对鸿蒙平台做额外修改。例如,时间范围筛选的TouchableOpacity组件,通过onPress事件触发setTimeRange状态更新,实现筛选状态的切换;设置按钮的onPress事件触发Alert.alert,实现弹窗提示,这些交互逻辑均可直接在鸿蒙平台运行。

值得注意的是,Alert组件的使用,RN与鸿蒙平台存在轻微差异,但通过RN的跨端适配层,鸿蒙可自动将RN的Alert转换为自身的弹窗组件,确保弹窗效果一致,开发者无需手动适配。这种“一次编写、多端适配”的特性,大幅降低了跨端开发的成本。

2.5 FlatList组件

应用排行榜需要渲染多个应用项,案例中选用RN的FlatList组件,而非ScrollView,核心原因是FlatList具备懒加载、复用组件的特性,可提升长列表的渲染性能,这对于鸿蒙设备(尤其是中低端设备)至关重要。

FlatList的核心配置(data、renderItem、keyExtractor)均采用RN原生语法,data绑定排序后的sortedApps数组,renderItem绑定封装好的renderAppItem组件,keyExtractor指定id作为唯一标识,确保列表渲染高效且不重复。同时,设置showsVerticalScrollIndicator: false,隐藏垂直滚动条,提升UI美观度,这种配置在鸿蒙平台中可直接生效。

FlatList的懒加载特性,可避免一次性渲染所有应用项导致的性能瓶颈——鸿蒙设备的内存资源有限,懒加载可减少初始渲染的组件数量,降低内存占用,确保应用运行流畅。这也体现了案例在设计时,不仅考虑了跨端复用,更兼顾了鸿蒙平台的性能需求。


RN代码能够适配鸿蒙OpenHarmony,核心在于鸿蒙平台对RN的适配层支持,以及开发者在代码编写时的跨端意识。结合本案例,总结以下3个关键要点,助力开发者快速实现RN代码到鸿蒙的适配。

第一,避免使用平台专属依赖和API。案例中未引入任何RN的平台专属组件(如Android的ViewPager、iOS的ScrollView),仅使用RN核心组件和API,确保代码的跨端兼容性。鸿蒙的适配层可完美解析RN核心组件和API,无需额外修改代码。

第二,统一资源格式。案例中使用Base64图标库,避免了不同平台图标格式(如Android的png、iOS的svg)不兼容的问题,确保图标在RN调试环境与鸿蒙设备中展示一致。同时,颜色、字体等资源,均采用标准格式,避免使用平台专属的资源常量。

第三,强类型约束与异常规避。通过TypeScript的类型定义,提前规避数据类型不匹配导致的跨端异常;同时,在数据处理逻辑中(如formatDataSize、getDataColor),增加边界判断(如Math.min(100, percentage)),避免因数据异常导致的组件渲染失败,这对于鸿蒙平台尤为重要——鸿蒙平台对组件渲染的异常容忍度较低,提前规避可提升应用的稳定性。


完成RN代码的开发与调试后,需通过打包命令,将RN代码转换为鸿蒙OpenHarmony可运行的文件,再集成到鸿蒙DevEco-Studio工程中,即可在鸿蒙设备中运行。具体流程如下:

第一步,执行打包命令。案例中使用npn run harmony命令(此处应为npm run harmony,推测为笔误),该命令是RN适配鸿蒙的专属打包命令,底层通过鸿蒙适配插件,将RN代码(JS/TS、组件、样式)转换为鸿蒙可识别的HAR(Harmony Archive)文件或HAP(Harmony Application Package)文件。打包过程中,适配插件会自动处理组件适配、API转换、资源统一等问题,无需开发者手动干预。

第二步,拷贝打包文件到鸿蒙工程。打包完成后,会生成对应的鸿蒙可运行文件(通常位于项目的dist/harmony目录下),将该文件拷贝到鸿蒙DevEco-Studio工程的对应目录(一般为entry/src/main/js/目录),即可完成RN打包文件与鸿蒙工程的集成。

第三步,鸿蒙工程调试与运行。集成完成后,在DevEco-Studio中配置鸿蒙设备(模拟器或真实设备),执行运行命令,即可在鸿蒙设备中看到RN开发的流量排行榜应用,且所有功能与RN调试环境中完全一致。

此处需注意两个细节:一是打包命令需提前在package.json中配置,确保npm run harmony命令可正常执行;二是拷贝文件时,需对应鸿蒙工程的目录结构,否则会导致集成失败,无法正常运行。


这段示例以一个“应用流量排行榜”为核心场景,展示了如何用 React Native 在不引入重型依赖的前提下,构建可在多端(包括 OpenHarmony 环境)运行的交互式列表页面。它刻意保持“少公共模块、内联资源、轻逻辑”的风格,以降低跨端适配难度,并为打包到鸿蒙侧的集成提供最顺滑的路径。

视图

组件层面采用函数式范式与 React 状态驱动。timeRangesortBy 两个状态形成页面的最小数据闭包,所有过滤、排序与渲染都由它们决定。这样做的好处是:

  • 渲染路径清晰:状态变化→JS 计算→虚拟 DOM diff→原生视图更新。跨端一致性主要由 React reconciler 与 RN bridge 保证。
  • Side effect 可控:排序在 JS 侧完成,避免平台间 I/O 或资源差的不同表现;OpenHarmony 环境也不需要额外的 native 统计模块。

在多端环境中,React Native 采用 Yoga 做布局计算,bridge 将 JS 侧的更新批次同步到原生,避免在鸿蒙端额外做 ArkUI 适配时带来布局差异。示例中所有 UI 都用 RN 官方组件(SafeAreaView、ScrollView、FlatList、TouchableOpacity、Text、View)完成,属于“跨端最小公倍数”。

列表与虚拟化

应用列表用 FlatList 进行虚拟化渲染,保证长列表场景下的帧率与内存占用。renderItem 在 JS 侧计算百分比与颜色,再用样式将进度条回填到 UI。需要注意的细节:

  • 进度条填写的 width: 'XX%' 在 RN 的 Yoga 与原生单位换算上是一致的;在鸿蒙兼容层通常走 Android pipeline,表现稳定。
  • keyExtractor 用 id 避免重复重绘;跨端虚拟化保持一致性,避免 ArkUI 接入后出现 diff 误判。

在某些设备上,阴影(elevation/shadow)渲染存在差异,示例给了轻度值,降低差异影响。若在 OpenHarmony 上发现阴影不一致,建议用描边与层次色块替代,保证视效统一。

交互

交互统一为 TouchableOpacity 配合 Alert.alert,这条链路的好处是:

  • 事件在 JS 线程上调度,bridge 将弹窗请求传到原生层;Android/鸿蒙侧有一致的弹窗通道。
  • 样式交互通过状态与样式切换完成(排序、时间范围),避免导航或复杂路由引入平台适配成本。

如果后续迁移到更“纯”的鸿蒙栈(ArkUI/ArkTS),也可以用覆盖层实现对 Alert 的等价替换,保持交互与动画表现一致。


真实演示案例代码:

// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  apps: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  chart: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  warning: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  wifi: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  more: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

const { width, height } = Dimensions.get('window');

// 应用流量数据类型
type AppUsage = {
  id: string;
  name: string;
  packageName: string;
  icon: string;
  mobileData: number; // MB
  wifiData: number;   // MB
  totalData: number;  // MB
  lastUsed: string;
  isSystem: boolean;
};

const DataUsageRankingApp: React.FC = () => {
  const [timeRange, setTimeRange] = useState<string>('today');
  const [sortBy, setSortBy] = useState<'total' | 'mobile' | 'wifi'>('total');
  
  // 模拟数据
  const appUsages: AppUsage[] = [
    { id: '1', name: '微信', packageName: 'com.tencent.mm', icon: '💬', mobileData: 125.4, wifiData: 320.8, totalData: 446.2, lastUsed: '刚刚', isSystem: false },
    { id: '2', name: '抖音', packageName: 'com.ss.android.ugc.aweme', icon: '📱', mobileData: 98.7, wifiData: 450.2, totalData: 548.9, lastUsed: '5分钟前', isSystem: false },
    { id: '3', name: 'QQ', packageName: 'com.tencent.mobileqq', icon: '💬', mobileData: 76.3, wifiData: 180.5, totalData: 256.8, lastUsed: '10分钟前', isSystem: false },
    { id: '4', name: '微博', packageName: 'com.sina.weibo', icon: '🐦', mobileData: 45.2, wifiData: 120.3, totalData: 165.5, lastUsed: '15分钟前', isSystem: false },
    { id: '5', name: '支付宝', packageName: 'com.eg.android.AlipayGphone', icon: '💳', mobileData: 23.1, wifiData: 85.7, totalData: 108.8, lastUsed: '20分钟前', isSystem: false },
    { id: '6', name: 'Chrome浏览器', packageName: 'com.android.chrome', icon: '🌐', mobileData: 110.6, wifiData: 280.4, totalData: 391.0, lastUsed: '25分钟前', isSystem: false },
    { id: '7', name: '腾讯视频', packageName: 'com.tencent.qqlive', icon: '📺', mobileData: 89.4, wifiData: 520.1, totalData: 609.5, lastUsed: '30分钟前', isSystem: false },
    { id: '8', name: '网易云音乐', packageName: 'com.netease.cloudmusic', icon: '🎵', mobileData: 15.8, wifiData: 95.6, totalData: 111.4, lastUsed: '35分钟前', isSystem: false },
    { id: '9', name: '手机系统', packageName: 'android.system', icon: '⚙️', mobileData: 5.2, wifiData: 12.3, totalData: 17.5, lastUsed: '40分钟前', isSystem: true },
    { id: '10', name: '系统服务', packageName: 'android.service', icon: '🔧', mobileData: 3.1, wifiData: 8.7, totalData: 11.8, lastUsed: '45分钟前', isSystem: true },
  ];

  // 按照当前排序方式排序
  const sortedApps = [...appUsages].sort((a, b) => {
    if (sortBy === 'mobile') return b.mobileData - a.mobileData;
    if (sortBy === 'wifi') return b.wifiData - a.wifiData;
    return b.totalData - a.totalData;
  });

  // 格式化数据大小
  const formatDataSize = (sizeInMB: number): string => {
    if (sizeInMB >= 1024) {
      return `${(sizeInMB / 1024).toFixed(1)} GB`;
    }
    return `${sizeInMB.toFixed(1)} MB`;
  };

  // 获取数据使用颜色
  const getDataColor = (percentage: number): string => {
    if (percentage > 80) return '#ef4444'; // 红色
    if (percentage > 50) return '#f59e0b'; // 橙色
    return '#10b981'; // 绿色
  };

  // 渲染应用项
  const renderAppItem = ({ item, index }: { item: AppUsage; index: number }) => {
    const totalUsed = 1024; // 模拟总使用量1GB
    const percentage = (item.totalData / totalUsed) * 100;
    
    return (
      <View style={styles.appItem}>
        <View style={styles.rankContainer}>
          <Text style={[
            styles.rankText, 
            index < 3 && styles.topRankText
          ]}>
            {index + 1}
          </Text>
        </View>
        
        <View style={styles.appIcon}>
          <Text style={styles.appIconText}>{item.icon}</Text>
        </View>
        
        <View style={styles.appInfo}>
          <Text style={styles.appName}>{item.name}</Text>
          <Text style={styles.packageName}>{item.packageName}</Text>
          <Text style={styles.lastUsed}>最后使用: {item.lastUsed}</Text>
        </View>
        
        <View style={styles.dataContainer}>
          <Text style={styles.totalData}>{formatDataSize(item.totalData)}</Text>
          <View style={styles.progressBar}>
            <View 
              style={[
                styles.progressFill, 
                { width: `${Math.min(100, percentage)}%`, backgroundColor: getDataColor(percentage) }
              ]} 
            />
          </View>
          <Text style={styles.percentageText}>{percentage.toFixed(1)}%</Text>
        </View>
      </View>
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>流量排行榜</Text>
        <TouchableOpacity 
          style={styles.settingsButton}
          onPress={() => Alert.alert('设置', '流量监控设置')}
        >
          <Text style={styles.settingsIcon}>⚙️</Text>
        </TouchableOpacity>
      </View>

      <ScrollView style={styles.content}>
        {/* 总览卡片 */}
        <View style={styles.overviewCard}>
          <View style={styles.overviewItem}>
            <Text style={styles.overviewValue}>2.4 GB</Text>
            <Text style={styles.overviewLabel}>本月总计</Text>
          </View>
          <View style={styles.overviewItem}>
            <Text style={styles.overviewValue}>1.8 GB</Text>
            <Text style={styles.overviewLabel}>移动数据</Text>
          </View>
          <View style={styles.overviewItem}>
            <Text style={styles.overviewValue}>0.6 GB</Text>
            <Text style={styles.overviewLabel}>WiFi数据</Text>
          </View>
        </View>

        {/* 时间范围选择 */}
        <View style={styles.filterContainer}>
          <Text style={styles.filterTitle}>时间范围</Text>
          <View style={styles.filterOptions}>
            {['今天', '昨天', '本周', '本月'].map((range, index) => (
              <TouchableOpacity
                key={index}
                style={[
                  styles.filterOption,
                  timeRange === ['today', 'yesterday', 'week', 'month'][index] && styles.activeFilter
                ]}
                onPress={() => setTimeRange(['today', 'yesterday', 'week', 'month'][index])}
              >
                <Text style={[
                  styles.filterText,
                  timeRange === ['today', 'yesterday', 'week', 'month'][index] && styles.activeFilterText
                ]}>
                  {range}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>

        {/* 排序选项 */}
        <View style={styles.filterContainer}>
          <Text style={styles.filterTitle}>排序方式</Text>
          <View style={styles.filterOptions}>
            {[
              { key: 'total', label: '总流量' },
              { key: 'mobile', label: '移动数据' },
              { key: 'wifi', label: 'WiFi数据' }
            ].map((option) => (
              <TouchableOpacity
                key={option.key}
                style={[
                  styles.filterOption,
                  sortBy === option.key && styles.activeFilter
                ]}
                onPress={() => setSortBy(option.key as any)}
              >
                <Text style={[
                  styles.filterText,
                  sortBy === option.key && styles.activeFilterText
                ]}>
                  {option.label}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>

        {/* 应用排行榜标题 */}
        <View style={styles.sectionHeader}>
          <Text style={styles.sectionTitle}>应用流量排行</Text>
          <Text style={styles.appCount}>{sortedApps.length} 个应用</Text>
        </View>

        {/* 应用列表 */}
        <FlatList
          data={sortedApps}
          renderItem={renderAppItem}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
        />

        {/* 数据使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.infoTitle}>数据使用说明</Text>
          <Text style={styles.infoText}>• 排行基于选定时间范围内的数据使用量</Text>
          <Text style={styles.infoText}>• 红色表示高流量使用,绿色表示低流量使用</Text>
          <Text style={styles.infoText}>• 点击应用可查看详情或限制权限</Text>
          <Text style={styles.infoText}>• 系统应用通常消耗少量后台数据</Text>
        </View>

        {/* 警告信息 */}
        <View style={styles.warningCard}>
          <Text style={styles.warningTitle}>⚠️ 流量提醒</Text>
          <Text style={styles.warningText}>您本月已使用 2.4GB,套餐剩余 0.6GB,建议开启WiFi节省流量</Text>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('首页')}
        >
          <Text style={styles.navIcon}>🏠</Text>
          <Text style={styles.navText}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('排行')}
        >
          <Text style={styles.navIcon}>📊</Text>
          <Text style={styles.navText}>排行</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('统计')}
        >
          <Text style={styles.navIcon}>📈</Text>
          <Text style={styles.navText}>统计</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('设置')}
        >
          <Text style={styles.navIcon}>⚙️</Text>
          <Text style={styles.navText}>设置</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  settingsButton: {
    width: 36,
    height: 36,
    borderRadius: 18,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
  },
  settingsIcon: {
    fontSize: 18,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  overviewCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  overviewItem: {
    alignItems: 'center',
  },
  overviewValue: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  overviewLabel: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 4,
  },
  filterContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  filterTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  filterOptions: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  filterOption: {
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 20,
    marginRight: 8,
    marginBottom: 8,
  },
  activeFilter: {
    backgroundColor: '#3b82f6',
  },
  filterText: {
    fontSize: 12,
    color: '#64748b',
  },
  activeFilterText: {
    color: '#ffffff',
  },
  sectionHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 12,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  appCount: {
    fontSize: 14,
    color: '#64748b',
  },
  appItem: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    marginBottom: 8,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  rankContainer: {
    width: 30,
    alignItems: 'center',
    marginRight: 12,
  },
  rankText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#64748b',
  },
  topRankText: {
    color: '#f59e0b',
  },
  appIcon: {
    width: 40,
    height: 40,
    borderRadius: 8,
    backgroundColor: '#e2e8f0',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  appIconText: {
    fontSize: 20,
  },
  appInfo: {
    flex: 1,
  },
  appName: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 2,
  },
  packageName: {
    fontSize: 12,
    color: '#94a3b8',
    marginBottom: 2,
  },
  lastUsed: {
    fontSize: 12,
    color: '#64748b',
  },
  dataContainer: {
    alignItems: 'flex-end',
    marginLeft: 8,
  },
  totalData: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
  },
  progressBar: {
    width: 80,
    height: 4,
    backgroundColor: '#e2e8f0',
    borderRadius: 2,
    marginBottom: 4,
  },
  progressFill: {
    height: '100%',
    borderRadius: 2,
  },
  percentageText: {
    fontSize: 12,
    color: '#64748b',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 22,
    marginBottom: 8,
  },
  warningCard: {
    backgroundColor: '#fef3c7',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  warningTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#d97706',
    marginBottom: 8,
  },
  warningText: {
    fontSize: 14,
    color: '#92400e',
    lineHeight: 20,
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
});

export default DataUsageRankingApp;

请添加图片描述



安装DevEco Studio程序

在这里插入图片描述
选择目标安装目录:

在这里插入图片描述
设置环境变量,但是需要重启一下:

在这里插入图片描述
新建一个空白模板:

在这里插入图片描述
设置API为24的模板项目:
在这里插入图片描述
初始化项目,自动下载相关依赖:

在这里插入图片描述


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述
本文以React Native开发的"应用流量排行榜"为例,探讨跨端适配鸿蒙OpenHarmony平台的关键技术。案例采用三层架构设计:核心业务层使用RN原生语法处理数据和UI渲染;跨端适配层通过组件适配和API兼容实现平台适配;平台部署层完成打包配置和工程集成。文章详细解析了类型定义、数据处理、UI渲染、交互逻辑等技术实现,重点阐述了如何通过避免平台专属依赖、统一资源格式、强类型约束等策略确保跨端兼容性。最后介绍了RN代码打包为鸿蒙可运行文件的具体流程,为开发者提供了RN转向鸿蒙跨端开发的可落地实践方案。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐