
去年年底接手了一个挺有意思的项目把一套React Native写的英雄联盟助手App移植到OpenHarmony平台。这个App功能不算复杂核心就是赛事资讯、英雄资料、战绩查询这几块但因为要上架到应用市场还得过XTS认证整个适配过程比想象中坑多得多。这篇文章就从最基础也最关键的一环说起——主导航的实现。为什么说主导航关键因为导航是App的骨架它决定了页面怎么组织、怎么跳转、怎么返回也间接影响着后续所有功能的开发方式。RNReact Native生态里最常用的就是React Navigation但这套方案到了OpenHarmony上能不能直接用、性能怎么样、跟鸿蒙原生的页面栈怎么协作都是需要实测的。这篇就把我在实战里验证过的方案、踩过的坑、以及最终沉淀下来的实现思路完整拆一遍给正在做同类移植的朋友一个参考。1. 项目背景与主导航设计思路1.1 RN on OpenHarmony 这套组合是怎么回事先交代一下技术背景。OpenHarmony是开源鸿蒙操作系统跟华为的HarmonyOS NEXT不是一回事但底层技术同源。RN要在OpenHarmony上跑靠的是社区维护的react-native-harmony这个适配库。它不是简单包装而是把RN的渲染引擎、JS引擎、原生模块桥接全部映射到鸿蒙的ArkUI框架上。实际用下来RN on OpenHarmony的架构是这样工作的JS侧代码由ArkTS运行时加载RN的Shadow Tree映射成ArkUI的组件树原生模块调用则通过JSIJavaScript Interface走鸿蒙的N-API或者直接调系统服务API。简单说你的RN代码几乎不用改但底层渲染和原生能力调用要换成鸿蒙的实现。这套方案目前最适配的是OpenHarmony 4.0及以上版本也适配HarmonyOS NEXTRN版本建议用0.72以上的release版因为社区修复了很多线程调度和内存管理的问题。如果你们项目还在用RN 0.65这种老版本说实话迁移成本会高不少很多底层接口对不上。至于为什么选RN而不是ArkTS重写主要是两个原因一是业务代码量大RN版本已经迭代了三年重写成本太高二是团队里前端工程师多RN是大家最熟的技术栈。Android和iOS两端的RN代码已经跑通了现在只需要补一个OpenHarmony适配层这是性价比最高的路线。1.2 主导航选型React Navigation还是鸿蒙原生容器主导航的实现方式摆在我面前的有两条路。第一条路是直接用React Navigation这是RN社区的标准方案。底部Tab用createBottomTabNavigator页面栈用native-stack这在Android和iOS上是零成本方案。理论上react-native-harmony对React Navigation的支持已经比较成熟JS侧的导航状态管理和页面跳转逻辑可以完全复用不需要重写。实际体验下来底部Tab的切换、页面压栈出栈这些基础操作跟手机上的原生体验差距不大。第二条路是混合架构外层用鸿蒙原生容器窗口或者Navigation组件承载RN页面RN内部再用React Navigation管理业务子页面。这个方案的好处是Tab栏、标题栏、侧滑返回这些都是系统级的观感上跟纯鸿蒙App完全一致但代价是要写不少ArkTS胶水代码两套导航状态还要做同步。我最终选了React Navigation为主、鸿蒙原生页面栈配合的方案。原因很简单团队对React Navigation非常熟社区的文档和模板也全遇到问题好排查。而且React Navigation在OpenHarmony上的性能经过实际压测底部Tab切换的帧率能稳定在50帧以上已经够用了。提示如果你的App页面层级极深、动画要求极高或者需要大量使用鸿蒙原生页面做特定交互混合架构会更合适。但如果是常规的业务AppReact Navigation完全能扛住别过度设计。1.3 主导航要解决的核心问题主导航不只是“放几个Tab按钮”这么简单它至少要解决四个问题。页面注册与映射RN的导航器里要声明所有页面OpenHarmony侧也得有一个页面路由表让系统知道某个URL对应哪个RN组件或者哪个原生页面。状态保持Tab切换时页面不能被销毁重建否则用户切走再切回来列表滚动位置就没了。React Navigation有自己的状态恢复机制但要注意在OpenHarmony低内存场景下可能会被系统回收需要做好状态持久化。原生页面与RN页面的互跳战绩详情页、召唤师信息页这些需要调用系统能力比如定位、相机的页面可能会用鸿蒙原生实现。那么在RN页面里点击一个按钮要能跳到原生页面原生页面操作完要能回到RN并刷新数据。这个双向通信是主导航必须解决的。事件与通知比如成就系统弹窗、比赛开始提醒这些是全局事件不管当前在哪个Tab、哪个页面都要能收到通知并跳转相应页面。React Navigation的navigationRef可以解决JS侧的事件分发但如果是原生模块发来的事件需要一个事件总线来中转。这四个问题都解决好了主导航才算真正立住了。2. 环境准备与工程接入2.1 OpenHarmony开发环境怎么搭在动手写导航之前先把工程跑起来不然一切白搭。OpenHarmony应用开发用的IDE是DevEco Studio这是官方工具目前推荐用5.x的版本支持API 10以上的SDK。同时需要安装Node.js版本建议16.x到18.x别用太新的版本RN的构建工具链对Node 20的支持在部分场景下还有兼容问题。接下来是SDK的管理。DevEco Studio自带SDK Manager你要确保安装了OpenHarmony的SDK包版本号跟你的目标设备匹配。我做这个项目时用的是API 10对应OpenHarmony 4.0这个版本的react-native-harmony适配最成熟建议你也从API 10起步。工程结构上react-native-harmony采用的是一个目录里同时存在RN工程和鸿蒙工程的模式。RN部分就是标准的npx react-native init出来的结构鸿蒙部分则是一个完整的DevEco工程放在harmony这个目录下。构建的时候先编译RN的JS Bundle再把这个Bundle和资源打包进鸿蒙应用里。我踩过的一个挺关键的坑JS Bundle的打包模式会影响调试效率。开发阶段要用debug模式走Metro Server热更新发布之前要跑bundle-for-huawei命令把JS代码打进鸿蒙APK其实是HAP包里。这两套流程要分开别混用。2.2 接入react-native-harmony适配层的操作步骤接入适配层其实就是把react-native-harmony作为依赖装进去然后做几个配置。第一步在RN工程的package.json里增加依赖对应的库版本要跟OpenHarmony SDK版本匹配。以API 10为例react-native的版本要选0.72.x社区提供的RNOH版本React Native OpenHarmony要选0.72.x对应的发布版本。版本差一点都可能出现接口对不上的情况这块真的不能偷懒直接用文档里推荐的组合版本最稳妥。第二步在鸿蒙工程里引入RNOH的ArkTS源码依赖包配置。特别要注意build-profile.json5里要配置好依赖仓库地址否则编译的时候拉不到包。第三步把RN的构建产物集成到鸿蒙工程中。最简单的方式是使用社区提供的脚手架工具自动生成一个鸿蒙工程骨架然后手动微调。我在项目里用的是react-native-ohos脚手架执行一条命令就能生成完整的工程文件节省了不少时间。第四步做原生模块的注册。如果后续你要调用定位、相机、拨打电话这些能力需要在鸿蒙侧注册对应的原生模块这样JS侧才能找到它。这一步在导航验证阶段可以先不配置等到做功能模块时再补。提示整个环境搭建的时间第一次做通常要一天到一天半其中有一半时间是在折腾依赖版本和IDE的配置。如果遇到编译报错优先去react-native-harmony的GitHub Issues里搜报错关键词大部分坑前人已经踩过了。2.3 第一个RN页面跑通验证环境搭好之后先别急着写导航先做一个最简单的验证在鸿蒙设备里显示一个Hello World页面。具体操作是用RN模板里的App.tsx替换成一段极简代码然后启动debug模式让App通过Metro Server加载bundle。只要你看到了“Hello OpenHarmony”的字样说明RN到鸿蒙的通道是通的接下来写导航才有意义。这个环节常见的坑是Metro Server连不上。原因往往是鸿蒙设备跟开发电脑不在同一个局域网或者防火墙拦了端口。解决办法是确认设备的IP能ping通电脑并且在Metro Server启动时指定正确的IP地址和端口。3. 主导航核心实现底部Tab与页面栈3.1 项目结构规划导航模块的代码组织我习惯这样分src/ ├── navigation/ │ ├── index.tsx // 导航器总入口 │ ├── types.ts // 导航参数类型定义 │ ├── AppNavigator.tsx // 根导航容器 │ ├── MainTabs.tsx // 底部Tab导航器 │ └── linking.ts // 外部链接与路由映射 ├── screens/ │ ├── HomeScreen.tsx // 首页赛事资讯 │ ├── ChampionsScreen.tsx // 英雄资料页 │ ├── MatchScreen.tsx // 战绩查询页 │ ├── ProfileScreen.tsx // 个人中心页 │ └── DetailsScreen.tsx // 通用详情页 ├── components/ │ └── ...这样的结构有一个好处导航跟业务页面解耦以后要加一个Tab或者改一个路由只需要在navigation目录里动刀不会影响其他业务模块。3.2 底部Tab导航具体怎么写底部Tab用的是React Navigation 6的Bottom Tabs组件。先安装依赖yarn add react-navigation/native react-navigation/bottom-tabs react-native-screens react-native-safe-area-context注意在OpenHarmony上react-native-screens要换成react-native-harmony的适配版本否则原生屏幕管理不会生效页面切换会明显卡顿。这是我的一个经验一定要确保所有依赖包都是鸿蒙兼容版本千万别直接在npm上拉最新的包。MainTabs组件核心代码大概是这样的import { createBottomTabNavigator } from react-navigation/bottom-tabs; import HomeScreen from ../screens/HomeScreen; // ... 其他页面 const Tab createBottomTabNavigator(); function MainTabs() { return ( Tab.Navigator screenOptions{{ headerShown: false }} initialRouteNameHome Tab.Screen nameHome component{HomeScreen} / Tab.Screen nameChampions component{ChampionsScreen} / Tab.Screen nameMatch component{MatchScreen} / Tab.Screen nameProfile component{ProfileScreen} / /Tab.Navigator ); }如果你在代码里还定义了Tab图标需要加载图片资源。在OpenHarmony环境下图片资源有两种方式一种是直接引用鸿蒙工程里的media资源一种是把图片打包进RN的assets目录。前者加载快但需要在ArkTS侧配置资源映射后者省事但图片资源多了以后包会变大。我的做法是底部Tab的图标走鸿蒙侧原生资源页面内部的图片走RN的assets。3.3 页面栈与根导航容器底部Tab管的是四个一级页面但像英雄详情这种二级页面不能让它在Tab内部展开应该push到整个导航栈的上层这样用户按返回键时才会直接退出二级页回到Tab而不是把整个Tab界面一起弹出。所以根导航容器要做一个嵌套结构import { NavigationContainer } from react-navigation/native; import { createNativeStackNavigator } from react-navigation/native-stack; import MainTabs from ./MainTabs; import DetailsScreen from ../screens/DetailsScreen; const Stack createNativeStackNavigator(); function AppNavigator() { return ( NavigationContainer Stack.Navigator Stack.Screen nameMain component{MainTabs} / Stack.Screen nameDetails component{DetailsScreen} / /Stack.Navigator /NavigationContainer ); }这样设计之后四个Tab页面永远在栈底Details页面永远在栈顶。从Details返回时只会回到Main不会把Tab的选中状态弄丢。这里有个细节值得注意native-stack在OpenHarmony上的表现跟iOS/Android有些差异。iOS上系统自带平滑的滑动手势返回Android上是边缘手势加系统返回键而OpenHarmony的设备横跨手机和平板返回手势有些设备支持、有些不支持。为了避免体验不一致我最终在导航器里禁用了边缘滑动手势统一用按钮返回加系统返回键。3.4 外部链接DeepLink配置除了App内部导航主导航还要支持DeepLink也就是从外部点击一个链接直接打开App内的某个页面。比如英雄联盟赛事页面里分享出去的比赛链接别人点开之后应该直接进到对应的赛事详情页。React Navigation提供了一个linking配置器可以把路径映射到路由const linking { prefixes: [lolassistant://, https://lolassistant.example.com], config: { screens: { Main: { screens: { Home: home, Match: match, }, }, Details: details/:id, }, }, }; function App() { return ( NavigationContainer linking{linking} AppNavigator / /NavigationContainer ); }鸿蒙侧还要在module.json5里声明想要接收的URL协议否则系统不会把外部链接分发到App里。我在项目里注册了自定义协议lolassistant://以及一个https域名。这里最容易踩坑的是域名校验鸿蒙系统要求声明这类关联域名前必须做域名所有权验证开发阶段可以直接用自定义协议绕过发布前如果要用https链接记得提前做验证流程。4. 页面跳转与Tab切换的联动细节4.1 从RN页面跳转到鸿蒙原生页面英雄联盟助手这个App里有几个页面必须用到鸿蒙原生能力召唤师信息编辑页要调相册和相机选头像战绩分享功能要调系统分享面板。这些页面用ArkTS写最方便因为它们直接跟系统API打交道。那么RN页面怎么跳到鸿蒙原生页面我走的是react-native-harmony提供的原生模块能力在鸿蒙侧封装一个NavigationModule把它注册成RN的原生模块// 鸿蒙侧代码示例 import { controller } from react-native-ohos/napi interface NavigationModule : NSObject RCTBridgeModule end RCT_EXPORT_MODULE(NavigationModule) RCT_EXPORT_METHOD(pushNativePage: (NSString *)pageName params:(NSDictionary *)params) { // 在这里调用鸿蒙的UIAbilityContext去拉起原生页面 }JS侧调用import { NativeModules } from react-native; const { NavigationModule } NativeModules; NavigationModule.pushNativePage(ProfileEditPage, { userId: 123456 });这里最关键的问题是原生页面返回时怎么通知RN页面刷新数据我的方案是事件广播。原生页面在关闭前通过全局事件总线emit一个事件RN侧提前注册监听。比如头像更新了原生页emit(avatarUpdated, { userId })RN的个人中心页面收到事件后就重新拉取用户信息。我自己的经验是事件参数里一定要带上足够的信息比如userId、pageName这样RN侧可以直接定位到绑定的页面去刷新避免到处广播搞出一堆僵尸监听器。4.2 从鸿蒙原生页面跳回RN页面并传参反过来鸿蒙原生页面想把参数传回给RN页面要用React Native提供的DeviceEventEmitter。原生侧发送事件// ArkTS侧 import { deviceEventEmitter } from ./RNOpenHarmony; deviceEventEmitter.emit(nativeReturn, { page: Details, params: { matchId: abc123, result: success, }, });RN侧接收事件import { DeviceEventEmitter } from react-native; useEffect(() { const subscription DeviceEventEmitter.addListener( nativeReturn, (data) { if (data.page Details) { navigation.navigate(Details, data.params); } } ); return () subscription.remove(); }, []);踩过一个坑设备从原生页面返回RN页面后RN的JS上下文可能会被重新创建如果之前被系统回收了这时候useEffect里注册的监听器其实已经失效了。要解决这个问题建议把事件监听的注册放在App入口级别用一个全局事件中心来管理而不是分散在各个页面里。4.3 Tab切换时页面状态保持与数据刷新策略Tab切换的体验用户很挑剔。你在英雄列表页往下翻了几屏切到战绩查询再切回来如果列表直接重置回顶部用户大概率会骂产品。React Navigation默认的机制是Tab页面切走之后并不会销毁组件仍然挂载在内存里所以状态能保持。但OpenHarmony的资源管理跟手机不太一样系统可能会在内存紧张的时候发出onTrimMemory信号如果React Navigation没有响应这个信号去做缓存释放反而容易出现卡顿。我的做法是结合useFocusEffect这个API在页面获得焦点的时候自动刷新数据在失去焦点的时候保存状态import { useFocusEffect } from react-navigation/native; import { useCallback } from react; useFocusEffect( useCallback(() { // 页面获得焦点时刷新赛事列表 fetchMatchList(); return () { // 页面失去焦点时可以做一些暂停操作比如暂停视频播放 }; }, [fetchMatchList]) );这个方案比在componentDidMount里拉数据更符合实际场景因为Tab切换不会重新执行componentDidMount但会触发focus和blur事件。数据不要求每次切Tab都拉所以要给fetchMatchList加上缓存判断数据新鲜度在5分钟内的直接复用缓存超时才重新请求。4.4 全局事件与导航联动从任意页面跳转目标的实现App里还有一个需求比赛开始前发送通知提醒用户用户点通知之后得跳到比赛直播页。这里跟用户的当前导航位置无关可能在首页可能在一级页面甚至可能在关掉App后的系统通知栏里。React Navigation提供了navigationRef可以脱离组件上下文直接操作导航import { createNavigationContainerRef } from react-navigation/native; export const navigationRef createNavigationContainerRef(); export function navigateToMatch(matchId: string) { if (navigationRef.isReady()) { navigationRef.navigate(Details, { matchId }); } }全局事件中心的逻辑则是把通知回调跟这个导航函数绑定eventCenter.on(match-start, (payload) { navigateToMatch(payload.matchId); });这套方案最大的好处是任何模块只需关心发事件不用管页面当前在哪、导航栈是什么状态。导航的状态管理集中在一个地方调试和维护都舒服。5. 实战中踩过的坑与性能调优5.1 底部Tab图标不显示的排查过程我接入底部Tab之后遇到一个很典型的问题四个Tab的label显示正常但图标区域是空的加载不出图片。排查思路是这样的。先看日志有没有报图像加载的错误。结果发现JS侧的报错是LegacyImageSourceResolver未定义这个明显是react-native的图片加载模块跟鸿蒙适配层没对接好。检查版本后发现react-native-svg和相关图片依赖只装了一部分有些依赖没有安装鸿蒙适配版本。最后解决方法是统一安装了react-native-harmony推荐的依赖集并且手动检查了package.json里所有带原生代码的库都换成鸿蒙版本图标一下子就好了。后来我把这条经验整理成了一份依赖兼容清单每次接入新库都先对照省了不少时间。5.2 页面切换闪烁问题第一个版本打开详情页时页面会先闪一下白屏然后再渲染出内容视觉上非常掉价。原因在于详情页的RN bundle加载是异步的页面压栈后初始是空帧等到JS字符串解析渲染完才出内容。解决方案是在原生侧用一个透明的Loading占位等React组件挂载完成之后再切换显示。具体来说在鸿蒙的页面容器里加上一个Loading状态监听RN的onLayout事件当RN侧第一个组件布局完成才把Loading隐藏。这个手段在iOS里是默认行为但OpenHarmony需要手动处理。5.3 导航切换时的帧率优化实录底部Tab切换我一开始用的纯JS侧动画结果在低端设备上能明显感觉到卡帧率目测也就20多帧。后面做了一次性能剖析两个优化手段效果最明显。第一个是启用react-native-screens的enableFreeze功能把不在当前页面的Tab页面冻结掉暂停它们的渲染和动画调用。这有点像浏览器里的后台标签页不干活就不占资源。启用了这个功能之后Tab切换操作的响应速度明显提升。第二个是图片资源优化。英雄列表页的头像、皮肤预览图都是大尺寸PNG原本加载时要解码拖慢了页面的渲染速度。改成WebP格式后页面整体渲染时间缩短了接近一半。这个优化对导航切换的整体体验帮助非常大。5.4 低内存设备上页面被回收的处理OpenHarmony平台上跑应用的设备内存差异极大从2GB到8GB都有。2GB内存的设备上如果用户开了不少后台应用RK页面很容易被系统回收。页面被回收之后用户切回App控制器会发现找不到原来的那个JS页面于是重建一个新的但原来的导航状态就丢了。要解决这个问题我用的是React Navigation的state持久化能力。简单说把导航状态序列化后存到本地磁盘App重启或者页面重建时恢复。具体是监听state变化事件防抖后写入存储import AsyncStorage from react-native-async-storage/async-storage; const onStateChange (state) { AsyncStorage.setItem(navState, JSON.stringify(state)); };启动时读取存储如果存在就直接作为初始状态传给NavigationContainer。注意恢复导航状态时要判断一下版本号避免应用升级后旧状态不兼容。这个恢复机制我实测下来体验不错用户不会因为页面被回收而强制回到首页。6. 相关辅助知识点RN调用电话功能、相机、XTS认证与HDI6.1 RN页面里能不能直接拨打电话用户需求里有一个很常见但容易忽略的点英雄联盟手游开黑前玩家想直接联系队友商量战术这时候App希望提供一键拨号功能。RN不能直接调系统电话要走鸿蒙原生模块。鸿蒙的call模块提供了makeCall接口通过它可以发起电话呼叫。需要注意这个接口在鸿蒙上是要权限声明的需要在module.json5里配置对应权限否则调用会被系统拦截。我在项目里的做法是封装一个TelephonyModule原生模块JS侧调用一个简单方法NavigationModule.makeCall(10086);同时要在UI上提示用户系统即将跳转到拨号界面以及告知通话产生的费用需要用户确认。这块涉及到用户隐私和资费敏感点产品评审的时候最好提前想清楚交互文案。6.2 OpenHarmony相机能力在RN里有几种用法英雄联盟助手里有更换头像的需求这就需要用系统相机拍照或者从相册选图。在OpenHarmony上相机能力的底层接口是ohos.multimedia.camera。在RN项目里有两种接入方式。第一种是隔离的在鸿蒙侧实现一个CameraView原生组件通过JSI通信把相机画面传给RN的View组件这种适合做AR识别、扫码这类实时画面需求。第二种是跳转式调系统相机应用拍完照片照片存到相册再通过相册能力选择照片。跳转式实现简单且不需要自己处理复杂的相机权限和生命周期问题。我的经验是如果只是换头像跳转式就够了。但如果你要做直播、要对英雄模型做AR展示那必须用原生组件方式。原生组件方式要在鸿蒙侧实现CameraKit的预览接口并且在RN组件生命周期变化时处理好相机的释放这个逻辑调试起来很考验耐心尽量别在导航功能还没稳定之前就上。6.3 XTS认证是什么为什么导航实现会影响过测XTSOpenHarmony Compatibility Test Suite兼容性测试套件在OpenHarmony生态里是个无法回避的环节。应用要上架到各厂商的应用市场或者要成为预置应用都需要通过XTS认证。这名词听起来很技术其实做的是兼容性验证确保你的应用在声明支持的设备上都符合平台规范。XTS认证里跟导航相关的测试项主要检查的是页面跳转是否遵循系统返回手势规范、应用是否能正确处理系统back事件、以及是否存在多余的页面栈残留。我在适配过程中吃过大亏最初我的返回逻辑里当用户在某个二级页面时按返回键会直接退出App而系统规范要求二级页面先返回到上一级页面再按一次才退出。这个行为跟iOS的物理无返回键逻辑不一样导致XTS的用例直接失败了一次。所以做主导航时心里就要有XTS这根弦。底部Tab的selected状态、push和pop的页面层级、返回键的处理逻辑都要按照鸿蒙的系统交互规范来做而不是照搬iOS或者Android的手势习惯。等到功能都做完了再回头修导航规范问题改动面会大很多。6.4 HDI在主导航架构里起到什么作用HDIHardware Device Interface硬件设备接口是OpenHarmony的硬件抽象层往上对接系统服务往下对接设备的底层驱动。它跟RN的导航代码离得很远但在做整个App适配时它决定了你调用某些硬件能力时的稳定性和一致性。举例来说相机模块在OpenHarmony上要经过HDI层操作硬件传感器如果某款设备的HDI实现有兼容性问题相机预览就会黑屏或者闪退。这类问题一旦出现是从上往下排查不出来的因为你的代码逻辑没错是底层接口的行为跟预期不符。我提这个是想给读者一个提醒RN on OpenHarmony的开发很多问题根本不是RN层的坑而是系统能力层的差异。导航功能本身上层代码写在各平台之间是共用的但底层渲染引擎在各平台上的稳定性会因为系统内核能力差异出现不同程度的问题。开发时建议在真机上多测几个型号尤其是不同芯片方案的设备尽早发现系统层的不一致。7. 主导航代码的后续演进空间从业务发展的角度来说主导航不可能一成不变特别是英雄联盟这种游戏资讯类App运营活动频繁未来很可能会临时增加活动Tab或者让某个Tab做角标焕新。React Navigation的方案下增减Tab页面非常简单只需要在MainTabs里加一行Tab.Screen。但要让这个简单动作跑流畅有一点值得提前设计把Tab配置独立成一个常量数组而不是散写在JSX里。这样活动运营想调整Tab顺序、切换角标状态时改配置就行不会误碰业务代码。新功能或者新页面接入时还要遵循一个原则页面组件不要直接依赖导航容器而是通过useNavigation和useRoute拿参数。这样后续就算把某个页面替换成原生页面组件代码也不会有大变动只需要在导航层的类型定义里做调整。另外团队内部有一个约定导航参数的类型定义必须写全禁止使用any。善用TypeScript的泛型和枚举约束这个在长期维护中能避免不少莫名其妙的运行时报错。导航是骨架骨架稳了上层的业务才能放心折腾。回想整个适配过程从环境搭建到今天完整的导航结构能稳定跑在OpenHarmony设备上花的时间远超预期但收获也很大。React Native跨端的能力借了OpenHarmony的壳在鸿蒙生态里落地生根这条路还很长。希望这篇关于主导航的实现记录能帮到正在这条路上探索的你。