ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React Native鸿蒙开发实战:从环境搭建到图片列表

React Native鸿蒙开发实战:从环境搭建到图片列表 1. 为什么我会选择React Native做鸿蒙开发架构与选型逻辑先把背景交代清楚我所在的团队一直用React Native维护一套跨平台AppiOS和Android都靠它。去年公司提出要支持鸿蒙系统最初我脑子里蹦出来的方案是“另起炉灶用ArkTS原生重写一套”。但评估完成本之后我改成走React Native适配鸿蒙这条路并且很快实现了从0到1跑通、再完成图片列表功能的全过程。这条路线对很多小团队来说是更务实的选择。你想一想如果你的业务模块主要是信息流、商品图、用户列表、表单页这类“以展示和交互为主”的场景整个团队已经熟练使用React、JS/TS生态那你只需要在现有RN工程里增加鸿蒙的构建目标就能复用到绝大部分业务代码。如果改用ArkTS重写意味着同样的页面要维护两套实现后续需求迭代的工作量翻倍。反过来如果你的产品重度依赖鸿蒙系统独有的能力元服务、意图框架、系统级卡片、CAPI互操作那原生ArkTS肯定更合适。这个判断我在项目启动前反复权衡过结论是列表页为主的工具型AppRN鸿蒙化是性价比最高的路径。再说说它的架构原理。React Native运行的基本机制是JS层写业务逻辑通过桥接机制调用原生组件和系统能力。你平时写的View、Text、Image并不是HTML标签而是映射到平台原生组件的“代理”。在iOS上它映射到UIView在Android上映射到ViewGroup/ImageView到了鸿蒙这里react-native-oh这个适配层会把它们映射到ArkUI的组件。也就是说JS代码不需要改或者改一小部分真正工作是搭建RN和鸿蒙原生之间的这座桥。我建议你在动手前先花半小时理解这条链路否则遇到“白屏”“组件不渲染”这类问题时会很懵。简单说你的JS代码经过打包器生成bundle鸿蒙端启动一个JS引擎去执行它再通过桥接对象把UI指令同步给ArkUI渲染。RN社区针对鸿蒙做了不少适配工作OpenHarmony为适配OpenHarmony和HarmonyOS的JavaScript运行环境先后支持过V8、QuickJS以及方舟运行时你在工程里可以看到对应引擎相关配置。我自己的项目用的是社区默认模板稳定性实测不错。关于具体版本我以当前npm上的react-native-oh/react-native-harmony作为核心依赖。它配合DevEco Studio使用把RN原生部分编译成鸿蒙的HAP包。这个过程和Gradle编译Android很像只是构建系统换成了鸿蒙的hvigor。作为小白你不需要深挖每一层但要记住三点第一鸿蒙运行RN靠的也是JS引擎执行bundle第二原生容器负责把RN组件渲染成ArkUI界面第三网络、存储、多媒体这些系统能力RN最终通过原生模块间接调用鸿蒙API。搞清楚了这些你再去看“为什么我的RN项目能在鸿蒙上跑”就会清楚很多。2. 环境搭建从零到跑通第一个RN鸿蒙项目2.1 准备工作清单我踩过的坑里第一个就是环境版本不对。早期的RN鸿蒙模板对Node版本、DevEco版本都有要求版本差太多会导致编译失败或者运行报错。这里列一份我实际验证过的环境清单Node.js 18 LTS或20 LTS建议用20打包速度快一些DevEco Studio 5.0及以上版本配套HarmonyOS SDK 5.0.0及之后版本鸿蒙构建工具hvigorDevEco一般会在首次打开项目时自动安装对应版本ohpm鸿蒙包管理器用于安装鸿蒙原生依赖一台运行HarmonyOS 4.0及以上或OpenHarmony的真机/模拟器在安装RN鸿蒙模板之前先确认Node和npm可用终端执行node -v和npm -v。DevEco Studio第一次启动会让你配置SDK记得把API 12以上的SDK装好因为新版RN鸿蒙适配依赖较新的系统接口。2.2 初始化RN鸿蒙工程初始化命令没有你想的那么玄乎npx react-native-oh/react-native-harmony init PictureListDemo cd PictureListDemo npm install如果你是第一次用这个模板npm install可能会耗时几分钟。装完之后看下项目结构它和标准RN工程非常相似有android、ios目录同时多了一个harmony目录。这个目录就是鸿蒙原生侧代码你在DevEco Studio里打开的就是它。之后还需要安装鸿蒙原生RN库ohpm install react-native-oh/react-native-harmony这一步是把RN的热更新框架、原生组件绑定、桥接层代码注入到鸿蒙工程里。很多教程会忽略这个步骤导致后面编译时报“找不到ReactNative包”的错误。2.3 用模拟器跑通Hello World如果你没有真机不用慌DevEco自带模拟器方案。启动DevEco Studio打开上面初始化出来的harmony目录等待项目同步完成。然后通过菜单Tools Device Manager创建或启动一个本地模拟器。这里说明一下如果你连开发机都没有虚拟化条件也没有实体手机还可以用DevEco内置的Previewer预览单个页面的效果虽然它不能完整跑RN应用但用来快速确认结构没问题。完整调试还是建议用本地模拟器或真机区别在于“预览”只做静态页面展示而“模拟器/真机”会真正执行你的JS bundle并调用系统API。第一次运行时控制台会输出一堆日志看到“Bundle URL is not set”或类似提示时不用紧张这是Metro没连接上。你需要另开一个终端执行npx react-native start启动Metro开发服务。等它显示Metro waiting on port 8081后再回到DevEco点击Run按钮。如果一切正常模拟器上会出现RN的欢迎页面这意味着你的RN鸿蒙环境已经通了。顺便一提真机上调试需要开启鸿蒙开发者模式和USB调试然后通过hdc鸿蒙设备连接工具识别设备保证手机和电脑在同一个局域网。跨端口转发时可以用类似hdc fport tcp:8081 tcp:8081的命令把电脑的Metro端口转发到手机具体语法以你安装的hdc help fport输出为准。2.4 构建HAP包当你需要把应用分发给别人测试或者上传到应用市场时就要构建HAP包。在DevEco中可以通过Build Build Hap(s)/APP(s)完成。构建产物通常在harmony目录下的build文件夹里后缀是.hap。命令行安装到设备用hdc install entry-default-signed.hap我建议开发阶段用DevEco的Run按钮它自动帮你签调试证书发布阶段再走命令行构建不然签错包会浪费不少时间。3. 图片列表实战数据请求、FlatList渲染与状态管理3.1 从Mock数据开始新手写图片列表最容易犯的错是一上来就接真实接口结果分不清是网络问题还是渲染问题。我的做法是先写死一个数组把UI跑通再接数据。创建一个mockData.js内容类似这样export const mockImages [ { id: 1, title: 蓝天与白云, thumbnail: https://picsum.photos/seed/1/400/300 }, { id: 2, title: 城市夜景, thumbnail: https://picsum.photos/seed/2/400/300 }, { id: 3, title: 山间小路, thumbnail: https://picsum.photos/seed/3/400/300 }, ];这里使用picsum.photos作为演示图源它的规则是固定的URL返回固定图片适合做列表测试。如果你的网络环境访问不通可以换成自己后端提供的图片地址或者干脆用一张本地图片配合不同的标题来模拟。关键是先保证“渲染”链路正确而不是数据本身。3.2 封装ListItem组件图片列表里每个Item我会单独拆出来因为后面要加“加载中”“加载失败”状态。一个可复用的Item长这样import React, { useState } from react; import { View, Text, Image, ActivityIndicator, StyleSheet, Pressable } from react-native; const ListItem ({ item, onPress }) { const [loaded, setLoaded] useState(false); const [error, setError] useState(false); return ( Pressable style{styles.card} onPress{onPress} View style{styles.imageWrapper} {!loaded !error ( ActivityIndicator style{styles.loading} color#999 / )} {error ( View style{styles.errorBox} Text style{styles.errorText}图片加载失败/Text /View )} Image source{{ uri: item.thumbnail }} style{styles.image} resizeModecover onLoad{() setLoaded(true)} onError{() setError(true)} / /View Text style{styles.title}{item.title}/Text /Pressable ); }; const styles StyleSheet.create({ card: { backgroundColor: #fff, borderRadius: 10, margin: 8, overflow: hidden, flex: 1, }, imageWrapper: { width: 100%, height: 180, backgroundColor: #f0f0f0, }, image: { width: 100%, height: 180, position: absolute, top: 0, left: 0, }, loading: { position: absolute, top: 50%, alignSelf: center, }, errorBox: { flex: 1, justifyContent: center, alignItems: center, }, errorText: { color: #999, }, title: { padding: 10, fontSize: 14, color: #333, }, });这里有几个细节值得注意。Image是绝对定位铺满容器这样即使loaded状态还没变成true它也会占据布局空间等加载完成后图片直接盖在上面视觉效果更平滑。加载失败时保留错误提示避免用户看到一片空白不知道发生了什么。开发列表页时Pressable比TouchableOpacity好用它天然支持点击态并且在新架构下性能更好。你的Item点击事件、长按事件、禁用态都可以通过它控制。3.3 用FlatList承载图片列表FlatList是RN生态里做长列表最核心的组件图片列表这种场景正好发挥它的优势。下面是完整的列表页面import React, { useEffect, useState } from react; import { FlatList, SafeAreaView, StatusBar, RefreshControl, Text } from react-native; import ListItem from ./ListItem; import { mockImages } from ./mockData; const PictureListScreen () { const [data, setData] useState([]); const [page, setPage] useState(1); const [refreshing, setRefreshing] useState(false); const [loadingMore, setLoadingMore] useState(false); const fetchData async (pageNum) { // 这里替换成你自己的接口 // 示例fetch(/api/images?page${pageNum}).then(res res.json()) return pageNum 1 ? mockImages : [...mockImages]; }; const loadFirstPage async () { const result await fetchData(1); setData(result); }; useEffect(() { loadFirstPage(); }, []); const handleRefresh async () { setRefreshing(true); await loadFirstPage(); setRefreshing(false); }; const handleLoadMore async () { if (loadingMore) return; setLoadingMore(true); const nextPage page 1; const more await fetchData(nextPage); setData((prev) [...prev, ...more]); setPage(nextPage); setLoadingMore(false); }; const renderFooter () { if (!loadingMore) return null; return Text style{{ textAlign: center, padding: 12 }}正在加载更多.../Text; }; return ( SafeAreaView style{{ flex: 1 }} StatusBar barStyledark-content / FlatList data{data} keyExtractor{(item) item.id} renderItem{({ item }) ListItem item{item} onPress{() console.log(item.id)} /} numColumns{2} refreshControl{ RefreshControl refreshing{refreshing} onRefresh{handleRefresh} / } onEndReached{handleLoadMore} onEndReachedThreshold{0.3} initialNumToRender{8} maxToRenderPerBatch{10} windowSize{11} ListFooterComponent{renderFooter} / /SafeAreaView ); }; export default PictureListScreen;我在Demo里设置了两列布局图片列表用两列展示信息密度更高。用numColumns而不是自己手动计算宽度是因为FlatList在内部会帮你把一行拆成多列并且适配不同屏幕宽度更省心。3.4 状态管理思路这里没有用Redux或者Zustand因为图片列表页的状态并不复杂。你需要管理的无非是第一页数据、当前页码、下拉刷新状态、上拉加载更多状态。用useState加useEffect就够了。不过如果你的列表页会和其他页面共享“选中图片”“浏览历史”等数据我建议把相对稳定的业务数据放到全局状态里而不是每个页面各自请求一遍。经验是页面内的临时UI状态用useState跨页面共享的业务数据才考虑全局状态库。一上来就引入Redux是小项目最常见的过度设计。4. 鸿蒙平台的适配差异网络权限、包配置与白屏问题排查4.1 鸿蒙网络权限声明在Android上发网络请求要声明INTERNET权限鸿蒙也一样但位置和文件名不同。你需要打开harmony/entry/src/main/module.json5在requestPermissions数组里加上{ requestPermissions: [ { name: ohos.permission.INTERNET } ] }不加这个权限你的图片URL是加载不出来的具体表现是列表空白、图片一直转圈控制台还会提示网络相关错误。很多初次接触鸿蒙开发的人在iOS/Android上跑得好好的代码拿到鸿蒙上就白屏或图片全挂首要怀疑项就是这里。另外一个容易踩的坑是明文HTTP请求。鸿蒙系统出于安全考虑默认会限制明文HTTP流量。如果你开发环境的后端接口是http://192.168.x.x:8080这种调试地址需要在module.json5中配置网络安全策略。具体做法是在对应模块的metadata中关联一个网络安全配置文件然后在resources/profile/下新建配置文件把需要放行的域名和cleartextTrafficPermitted设为true。不同SDK版本对配置文件的字段名略有差异你的工程模板里一般会自带示例以DevEco工程的提示为准。4.2 启动白屏的完整排查链路启动白屏是RN鸿蒙开发中遇到率最高的问题它的现象跟Android早期RN调试一模一样。我遇到白屏时按下面顺序排查基本都能定位确认Metro是否运行。RN应用启动后第一件事是拉取JS bundle。如果Metro没启动应用会卡在启动页或者白屏。终端执行npx react-native start看到Metro waiting on port 8081才算就绪。确认端口可达。在电脑上访问http://localhost:8081/status能看到packager-status:running说明Metro正常。如果访问不了检查端口占用。确认真机/模拟器能访问到电脑的8081端口。本地模拟器一般没问题真机就复杂一些可能涉及同一局域网、防火墙、USB端口转发。看DevEco日志。DevEco的Log窗口中筛选ReactNative关键字如果能看到Downloading bundle...或者Loading JS bundle输出说明bundle请求已经发出去了。如果一直卡在这里就是网络层问题。检查bundle加载完成后的报错。如果bundle加载完但页面白屏多半是JS层抛了异常。这种问题只能通过日志定位建议搭配console.log在React入口文件里输出标记。我自己遇到的白屏案例几乎都是Metro没开或者网络权限没配好。所以别急着怀疑RN鸿蒙不稳定先把上面链路走一遍。4.3 调试技巧日志、抓包与远程调试鸿蒙开发时我比较依赖两组工具一组是DevEco自带的Log工具另一组是hdc命令行。前者看页面渲染、JS异常、原生模块调用记录后者拿设备状态、装包、端口转发。如果你是做接口调试想确认网络请求到底有没有发出去建议先在代码里打日志确认请求的前后链路或者使用Charles这类HTTP调试工具分析流量。鸿蒙的抓包配置方式和Android不太一样牵扯到系统证书信任和代理配置网上资料不少但我的建议是开发环境能不开代理就不开代理优先用后端接口的响应日志来排查减少一层干扰。另外RN鸿蒙支持通过DevEco直接调起调试菜单修改bundle地址、开启远程调试。小白阶段遇到页面问题先在JS入口文件里输出几行关键日志比任何黑科技都管用。5. 列表性能与加载体验优化缓存、占位图与内存实测5.1 图片缓存与加载状态RN内置的Image组件在iOS/Android上的缓存策略比较成熟在鸿蒙上却需要多做一步。鸿蒙端的图片加载默认可能没有完整的磁盘缓存导致你上下快速滚动时图片反复请求列表滚动变得一顿一顿。我的方案分两层第一层保持Image组件自带的网络缓存设置检查source中是否按需要传cache字段第二层为高频访问的图片在业务层加一个简单的磁盘缓存模块。如果项目进度紧先把第一种做了再观察滚动表现。实际上RN鸿蒙模板的原生图片组件封装了大量的底层加载逻辑很多常规场景直接可用我测试下来稳定性和速度都在可接受范围。5.2 FlatList参数调优图片列表优化重点不在图片本身而在FlatList的渲染节奏。下面是几个我调过的参数以及我的使用值参数作用我的建议initialNumToRender首屏渲染条目数8~10太多会拖慢启动maxToRenderPerBatch每批渲染条目数8~10该值过大会掉帧windowSize预渲染窗口高度倍数11左右过大浪费内存过小滚动白屏removeClippedSubviews是否回收不可见视图谨慎开启鸿蒙低版本可能引发渲染异常onEndReachedThreshold触发加载更多的距离比例0.3~0.5太小容易来不及加载这些参数不是越大越好最好结合自己的图片尺寸和机器性能实测。我在低端模拟器上把initialNumToRender从10降到8首屏加载时间明显缩短。如果你发现滚动时图片占位闪烁检查windowSize是不是设置得太小。5.3 占位图与内存占用图片列表的另一大问题是内存。假设你有1000张图每张2MB全加载进内存会直接撑爆。所以我推荐三个习惯缩略图URL列表里永远不要用原图URL让后端提供一个400px宽度的缩略图点击大图再加载高清图。固定宽高的占位容器给图片容器设置固定宽高图片加载完成后直接填充避免列表滚动时因为图片高度未知导致跳跃。onLoad之后再做视觉反馈图片加载完成前用ActivityIndicator展示加载状态这里的核心技巧是容器固定高度避免图片出现时顶动后续内容。5.4 关于启动白屏的“残余问题”前面说的启动白屏这里补充一个后续容易遇到的场景bundle明明加载成功页面也渲染出来了但是第一屏偶尔会出现短暂白屏随后才显示图片列表。这种情况大概率是原生侧启动页到RN首帧的过渡时间太长或者首屏图片加载时占位图空缺。我给的做法是在原生启动页中保留一个白色背景的loading动画同时把列表首屏的initialNumToRender调大一点让第一批数据在用户看到之前就渲染好。再配合第一节讲到的JS引擎预热体验会好很多。写在最后的一点体会从立项到跑通图片列表功能我最大的感触是React Native适配鸿蒙这件事比想象中成熟也比想象中有细节。成熟在于RN的语法和体系基本不用改很多通用问题社区都有讨论细节在于鸿蒙原生侧的配置项、权限声明、构建链路和Android确实不一样每换一个平台都要重新踩一遍类似“清理缓存、检查权限、看日志”的循环。这套基本功才是跨平台开发真正值钱的部分。图片列表只是一个起点后面你如果再接入表单、详情页、多图片上传底层思路完全一样先确认原生侧能力再封装JS层最后在真机验证体验。希望这篇分享能帮你少走几步弯路。
返回列表