RN for OpenHarmony上的英雄联盟助手App,主导航我是这么一步步搭起来的
前阵子一直在折腾一件事:在OpenHarmony设备上把React Native工程跑起来,然后做一个非官方的英雄联盟助手App原型,先把主导航这条主链路打通。说实话,这个组合初看有点冷门——RN生态成熟,OpenHarmony的适配层还在成长期,两者凑到一块,大多数人的第一反应是"文档齐吗?能跑通吗?坑多不多?"
我的回答是:能跑,坑确实不少,但主导航这种核心骨架反而是整个项目里最好落地的一块,因为它不涉及太深的系统能力,只要把路由、页面栈和状态衔接想清楚,剩下的是纯粹的前端工程问题。这篇文章就是这次实操的完整记录,从选型到环境搭建、从导航代码到实机调试踩坑,适合两类人看:一类是想在OpenHarmony上试RN的移动端开发者,另一类是准备做工具/助手类App但不确定用什么跨端方案的技术负责人。
1. 这个技术组合要解决什么问题:OpenHarmony上的RN落地背景
1.1 跨端框架在OpenHarmony生态里的位置
先说背景。OpenHarmony开放原子开源基金会这些年把系统基础能力铺得很快,但应用生态和Android/iOS没法比,存量应用迁移是个现实问题。原生开发用的是ArkTS/ArkUI这套,语言和框架都是新的一套,团队要上手得重新学习;如果内部已经有一堆用React或React Native沉淀下来的业务代码、组件库和工程经验,那React Native for OpenHarmony就是一个值得评估的折中路线。
这个适配路线不是社区里某个个人开发者拍脑袋搞的,OpenHarmony SIG组织在维护react-native-harmony仓库,目标就是把RN的核心渲染和原生能力映射到OpenHarmony上。RN跑在OpenHarmony设备上的原理,和它在Android/iOS上是一样的:JS引擎执行JavaScript层逻辑,原生侧通过包管理器加载RN框架层,最终UI渲染走鸿蒙的原生组件。对应用开发者来说,大部分React代码可以照常写,只是在工程集成和少数原生模块调用上需要做适配。
不过这里要泼一盆冷水:RN for OpenHarmony的能力边界比几个主流平台窄,尤其是涉及底层原生动画性能、复杂手势、特殊字体渲染的场景,适配层的成熟度会直接影响体验。所以我给自己定了个筛选条件——做信息密度中等、以列表和详情页为主的工具类App,而不是高帧率游戏那种对性能和原生能力要求极高的东西。英雄联盟助手里面的英雄资料、赛事信息、战绩查询这些模块,恰好就是这个量级。
1.2 游戏助手类App为什么适合拿RN先试
游戏助手类App和电商、内容社区不一样,它的核心场景很固定:用户打开App,第一件事是看今天有什么版本更新、英雄胜率变化,然后去查某个英雄的出装符文,或者看赛事日程和结果。这些场景的页面形态高度结构化,列表页、详情页、筛选页占了绝大多数,页面跳转关系虽然深但很规律。
这种结构对导航框架是友好的。导航的本质是管理一堆页面之间的跳转、参数传递、返回路径和状态保持,页面固定、跳转清晰的App正好可以把导航这套东西吃透。反过来,如果用RN在OpenHarmony上做一个需要大量Canvas绘制、复杂动画实时渲染的App,那适配层的性能瓶颈会直接劝退你。我的判断是:选RN做OpenHarmony应用,第一个验证项目最好是"列表+详情+底部主导航"的形态,跑通了就说明主链路没问题。
1.3 这个项目的基本盘
整个项目的目标我定得很保守:做一个英雄联盟助手的非官方原型,不接入任何真实账号系统,数据先用本地mock和少量公开接口撑起来。第一阶段的验收标准就是——App冷启动后进入主导航,五个一级入口能正常切换,每个入口下的二级页面能完成列表到详情的跳转,返回手势和系统返回键行为正确,页面间参数传递稳定。说白了,先把骨架立起来,再谈内容。
接下来各章节就按这个验收标准展开。
2. 主导航不是画几个Tab那么简单:需求倒推结构设计
2.1 助手类App的功能地图:哪些模块必须先进首屏
动手写代码之前,先把功能地图画出来。英雄联盟助手类App里,用户高频使用的东西无非四块:
- 资讯中心:版本公告、英雄改动、活动推送。这是日活的主要入口,用户打开就想看一眼今天有没有新东西。
- 英雄库:英雄列表、技能详情、出装推荐、符文推荐、克制关系。这是查询型场景,路径是"搜索/筛选→英雄列表→英雄详情→出装/符文展示"。
- 赛事中心:赛程列表、实时比分、战队积分榜、比赛详情。这是典型的"每半小时刷新一次"的场景。
- 个人中心:登录态、偏好设置、历史查询记录。在原型阶段可以先弱化。
那主导航到底放几个Tab?网上很多团队一上来就整四五个甚至六七个Tab,结果每个Tab里面又是各种二级入口,用户根本找不着北。我的处理方式是"主功能对齐高频场景"——资讯、英雄、赛事、个人中心四个Tab,外加一个居中凸起的"查询"按钮用来承载战绩查询这个最高频动作。战绩查询为什么不做成独立Tab?因为它的业务形态是"输入召唤师ID→跳历史对局列表→进入单局详情",是一条垂直路径,不是长期驻留的场景。放主导航只会把首屏塞得更满。
2.2 主导航与二级导航的职责划分
这个划分我想了很久,最后用一句话说清楚:主导航管"你今天要去哪个模块",二级导航管"你在某个模块里走到哪一步了"。
底部四个Tab是主导航,它们各自持有独立的页面栈。比如英雄Tab的栈是"英雄列表→英雄详情→出装详情",赛事Tab的栈是"赛程列表→比赛详情→BP阶段/选手数据"。两个Tab之间互不干扰——你在英雄Tab翻到三层以下,切到赛事Tab再切回来,英雄Tab应该还停留在原来的位置,页面不能被重建,滚动位置也不能丢。这是导航设计里最基础但也最容易被忽视的体验点。
层级要控制住。我给自己定的规矩是:任何一个模块的页面栈最多四层,超过四层就说明产品设计出了问题,该把某些信息挪到弹层或横向切换里。这个规矩在原型阶段帮我砍掉了很多不必要的页面。
2.3 路由表先行:把页面关系画成一张可维护的图
代码写一行之前,我先建了一张路由表,类似于集中式的路由配置,把每个页面的路由名、对应组件、需要的入参和返回值都列清楚。以下是我实际使用的路由表(节选):
| 路由名 | 所属模块 | 组件 | 入参 |
|---|---|---|---|
| NewsList | 资讯Tab | 资讯列表页 | 无 |
| NewsDetail | 资讯Tab | 资讯详情页 | newsId |
| HeroList | 英雄Tab | 英雄列表页 | 可空,支持position |
| HeroDetail | 英雄Tab | 英雄详情页 | heroId |
| HeroBuild | 英雄Tab | 出装符文页 | heroId, buildVersion |
| MatchSchedule | 赛事Tab | 赛程列表页 | 可空,支持date |
| MatchDetail | 赛事Tab | 比赛详情页 | matchId |
| SummonerSearch | 查询Tab | 战绩查询入口 | 无 |
| SummonerRecord | 查询Tab | 历史对局列表 | summonerName, region |
| MatchReport | 查询Tab | 单局战绩详情 | reportId |
这张表不是给人看的,是给代码里的导航类型定义用的。TS类型安全是第一步,有了这张表我才敢在做参数传递时用强类型约束而不至于到处写any。后面章节涉及代码的地方都会围绕这张表展开。
3. 工程初始化:版本匹配与最小可运行路径
3.1 版本矩阵:RN、OpenHarmony SDK、IDE三者怎么配
OpenHarmony的RN适配有个特点:不是RN官方发布一个新版本就立刻跟上的。社区适配仓的版本往往滞后、对应关系也复杂。刚开始我不信这个,直接装了最新版RN,结果在集成阶段就被卡住了,翻着文档来来回回试了很久,最后老老实实按推荐组合来。
我自己验证过能稳定跑的版本组合是:
| 组件 | 版本 |
|---|---|
| React Native | 0.72.x |
| react-native-harmony | 对应0.72.x的适配版 |
| OpenHarmony SDK | API 11及以上 |
| DevEco Studio | 5.0+(支持OpenHarmony工程) |
| Node.js | 18 LTS |
核心原则是:RN版本和react-native-harmony适配版本必须严格对应,不要自己"优化"。RN的大版本升级会改Bundle结构、改动原生桥接口,适配仓来不及跟上就会导致渲染层起不来。API level倒是不用卡太死,但低了有些系统能力会缺失,比如网络请求权限、文件存取这些,我在API 11上跑通了整套导航链路。
3.2 把RN工程导进OpenHarmony工程的正确姿势
集成方式上,react-native-harmony的推荐路径是两步走:先用React Native官方脚手架创建一个标准RN工程,再把HarmonyOS的原生工程作为配套工程引入,让RN源码层和OpenHarmony原生层共存于同一个项目目录下。
实际操作中的目录形态大概是这样的(简化版):
project-root/ ├── src/ # RN业务代码 │ ├── navigation/ │ ├── screens/ │ ├── components/ │ └── App.tsx ├── harmony/ # OpenHarmony原生工程 │ ├── entry/ │ │ ├── src/main/ │ │ │ ├── ets/ # ArkTS入口与自定义绑定逻辑 │ │ │ ├── resources/ │ │ │ └── module.json5 │ ├── hvigorfile.ts │ └── oh-package.json5 ├── package.json └── metro.config.jsRN那边用Metro打包JS Bundle,OpenHarmony原生工程通过加载Bundle来渲染。离线模式下,Bundle要随应用打包进去,路径配置在ArkTS入口处。
这一步的核心坑是:不要试图把原生工程生成的代码结构改掉。我一开始以为可以把RN的src目录和harmony/entry的代码结构合并成一个Flat结构,结果Metro的解析路径乱掉,折腾很久才明白,官方推荐的目录边界是有道理的。
3.3 跑通第一个Hello页要过的三关
把最小RN页面跑到OpenHarmony模拟器上,这个过程分三个阶段,每个阶段都有关卡要过。
第一关是原生构建。用DevEco Studio打开harmony目录之后,首次Sync要拉不少依赖,网络不好的话直接卡死。我建议先在Configure里把Maven镜像和npm镜像指好再Sync。编译过程中常见报错是oh-package.json5里的依赖版本和react-native-harmony不匹配,这个只能比对官方仓库示例工程里的锁版配置,没有捷径。
第二关是Metro启动。跑hello页面时需要在命令行启动Metro开发服务,然后让原生工程去连接。注意OpenHarmony模拟器里访问宿主机不能用localhost,要用宿主机局域网IP。如果你在配置里直接写localhost,设备端连不到,页面就白屏。
第三关是Bundle加载。开发模式下,原生工程会在启动时从Metro拉Bundle;真机上这一步对网络环境要求比较敏感。如果出现"Unable to load script"提示,先确认Module.json5里的网络权限有没有开,其次确认IP和端口是否可访问。
过了这三关,RN环境算真正立住了,接下来才敢碰导航。
4. 主导航的代码落地:选型、骨架与页面栈
4.1 导航库选型:为什么主链走JS实现
RN社区主流的导航库是React Navigation,它底层有两个核心依赖:react-native-screens负责原生屏幕栈优化,react-native-safe-area-context负责安全区适配。问题来了——这两个库在OpenHarmony上的原生适配情况并不完整,react-native-screens如果能力缺失,createNativeStackNavigator的性能优势就无从谈起。
我做了个取舍:主链路由全部走React Navigation的纯JS实现,不依赖原生屏幕栈。也就是底部Tab用createBottomTabNavigator,二级Stack用createStackNavigator而不是createNativeStackNavigator。代价是转场动画略糙、页面栈内存占用略高,收益是稳定性和可预测性——至少不会因为某个原生接口没映射到位就整个导航白屏。
这个选择可能让一些人意外,但做过跨端适配的人都能理解:在生态不成熟的平台上,稳定压倒性能。后期如果适配层完善了,再切native-stack只需要改几行配置。
4.2 底部Tab栏实现:五个Tab与图标资源的处理
主导航的核心代码并不复杂,难的是把依赖和环境配顺。我用的是React Navigation 6.x系列,安装依赖时必须确保以下包版本配套:
npm install @react-navigation/native @react-navigation/bottom-tabs @react-navigation/stack npm install react-native-screens react-native-safe-area-context npm install react-native-gesture-handler注意react-native-gesture-handler需要放在入口文件最顶部引入,否则手势库的事件总线和导航的响应链对不上。这个顺序问题在OpenHarmony上比Android更明显,因为适配层的原生手势事件在接入时机上更严格。
主导航的骨架代码:
// src/navigation/MainTabs.tsx import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { NavigationContainer } from '@react-navigation/native'; import NewsStack from './stacks/NewsStack'; import HeroStack from './stacks/HeroStack'; import MatchStack from './stacks/MatchStack'; import MineStack from './stacks/MineStack'; import SummonerSearchScreen from '../screens/search/SummonerSearchScreen'; const Tab = createBottomTabNavigator(); export default function MainTabs() { return ( <NavigationContainer> <Tab.Navigator initialRouteName="NewsStack" screenOptions={{ headerShown: false, tabBarActiveTintColor: '#0ac18e', tabBarInactiveTintColor: '#8a8a8a', tabBarStyle: { backgroundColor: '#121a26' }, }} > <Tab.Screen name="NewsStack" component={NewsStack} options={{ tabBarLabel: '资讯' }} /> <Tab.Screen name="SummonerSearch" component={SummonerSearchScreen} options={{ tabBarLabel: '查询' }} /> <Tab.Screen name="HeroStack" component={HeroStack} options={{ tabBarLabel: '英雄' }} /> <Tab.Screen name="MatchStack" component={MatchStack} options={{ tabBarLabel: '赛事' }} /> <Tab.Screen name="MineStack" component={MineStack} options={{ tabBarLabel: '我的' }} /> </Tab.Navigator> </NavigationContainer> ); }图标这块是一个独立的麻烦。React Navigation里最常见的做法是配合react-native-vector-icons使用自定义字体图标,但这个库在OpenHarmony适配层上对自定义字体的加载支持不完整,我试了几次始终有字体文件找不到的问题。最后干脆放弃字体图标,改用纯图片资源,就是png按需加载,一个Tab配一张普通态和一张选中态:
options={{ tabBarLabel: '资讯', tabBarIcon: ({ focused, color, size }) => ( <Image source={focused ? icons.newsActive : icons.newsNormal} style={{ width: size, height: size, tintColor: focused ? '#0ac18e' : color }} /> ), }}实测下来图片方案性能并不差,因为Tab icon尺寸固定且图很小,内存占用完全可以忽略。对OpenHarmony这种还在迭代的生态,少依赖一个原生模块就少一个崩溃点,这句话是我做这个项目最大的体会。
4.3 每个Tab内的页面栈与参数传递
Tab外壳搭好之后,核心工作量在每个Tab内部的Stack。以英雄Tab为例,它的链路是"列表→详情→出装符文",三层的栈结构:
// src/navigation/stacks/HeroStack.tsx import { createStackNavigator } from '@react-navigation/stack'; import HeroListScreen from '../../screens/hero/HeroListScreen'; import HeroDetailScreen from '../../screens/hero/HeroDetailScreen'; import HeroBuildScreen from '../../screens/hero/HeroBuildScreen'; const Stack = createStackNavigator(); export default function HeroStack() { return ( <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: '#121a26' }, headerTintColor: '#ffffff', cardStyle: { backgroundColor: '#1c2533' }, }} > <Stack.Screen name="HeroList" component={HeroListScreen} options={{ title: '英雄库' }} /> <Stack.Screen name="HeroDetail" component={HeroDetailScreen} options={{ title: '英雄详情' }} /> <Stack.Screen name="HeroBuild" component={HeroBuildScreen} options={{ title: '出装符文' }} /> </Stack.Navigator> ); }列表跳详情时的参数传递,结合前面那张路由表,我用TS类型把所有路由参数约束起来,页面组件直接用useRoute取参:
// src/types/navigation.ts export type HeroStackParamList = { HeroList: { position?: number } | undefined; HeroDetail: { heroId: string }; HeroBuild: { heroId: string; buildVersion: string }; };跳转的时候:
// HeroListScreen.tsx,列表项点击事件 navigation.navigate('HeroDetail', { heroId: item.heroId });这里有个值得强调的细节:导航参数不要传整个对象,只传ID,详情页自己再按ID取数据。很多人图省事把整个英雄对象放进params,结果页面数据过期了都不知道,而且serialize大对象在跨端平台上的性能损耗很直接。传ID是导航参数设计的铁律。
4.4 赛事Tab的动态切换:顶部三段式导航的联动
赛事Tab比英雄Tab多一个交互层次:顶部是"赛程/积分榜/战队"三段切换。这里如果每切一次都重新入栈一个页面,导航层级会很乱。正确做法是用页内状态做横向切换,只有"列表→详情"这种跨层级才走导航栈。
我抽了一个SegmentContainer组件,专门处理顶部分段切换:
// src/components/SegmentContainer.tsx const segments = ['赛程', '积分榜', '战队']; export default function SegmentContainer({ activeIndex, onChange, children }) { return ( <View style={{ flex: 1 }}> <View style={styles.segmentBar}> {segments.map((item, index) => ( <Pressable key={item} style={[styles.segmentItem, index === activeIndex && styles.activeSegment]} onPress={() => onChange(index)} > <Text style={index === activeIndex ? styles.activeText : styles.normalText}> {item} </Text> </Pressable> ))} </View> {children[activeIndex]} </View> ); }使用的时候,MatchScheduleScreen内部维护一个activeTab状态,切换只更新状态,不触发路由变化。只有当用户点击某场比赛进入详情时,才调用navigation.navigate('MatchDetail', { matchId })。
这个设计的核心原因是:赛事Tab的三段内容属于同一层级信息的不同维度,用户来回切换的频率很高,走路由会导致每次切换都重建整个页面,滚动位置全丢,而且转场动画会让人觉得很碎。用页内状态切,流畅度和记忆性都更好。
5. 导航接通数据层之后:骨架变业务的第一步
5.1 数据请求与页面缓存的矛盾
导航架子搭好之后,下一步就是把真实数据接进去。这时候立刻暴露一个问题:Tab切换本身不会触发页面重新渲染,但赛事数据、英雄胜率这些内容的时效性很强——用户从资讯Tab切到赛事Tab,看到的应该是刚刷新的数据,而不是几分钟前缓存的那份。
React Navigation对Tab切换默认是"挂载后保持在引用中,不销毁",这是为了保留滚动位置。但它不会主动帮你重新拉数据。我的方案是监听Tab的聚焦事件,在聚焦时做"静默刷新":
import { useFocusEffect } from '@react-navigation/native'; import { useCallback } from 'react'; function MatchScheduleScreen() { const [matchList, setMatchList] = useState([]); const [refreshing, setRefreshing] = useState(false); useFocusEffect( useCallback(() => { let active = true; fetchMatchList() .then((data) => { if (active) { setMatchList(data); } }) .catch(() => { // 静默失败,保留上次数据 }); return () => { active = false; }; }, []), ); }这里有几个讲究:active标志位用来防止异步回调在页面失焦后仍更新状态,这是React Navigation高频踩坑点;"静默失败保留旧数据"而不是弹错误提示,是因为用户只是在切Tab,不是主动刷新,打断感太强没必要。
5.2 下拉刷新和Tab切换时的加载态处理
数据加载态和导航状态经常打架。我遇到过的情况是:在资讯页下拉刷新拉了一半,用户切到英雄Tab再切回来,刷新回调才回来,页面已经重新聚焦,但刷新控件还挂在那里。
解决思路是把"刷新状态"和"页面是否可见"解耦。下拉刷新控件本身有状态,但触发逻辑放在页面可见的时候。我在每个列表页用一个自定义的刷新组件,它内部监听Tab可见性,如果页面已不可见就自动收起刷新动画,不等待数据返回。
5.3 路由参数的类型约定:避免一搜就崩的蠢问题
查询场景最容易出问题的是参数约定。SummonerSearchScreen会让用户输入召唤师ID和区服,跳转到SummonerRecordScreen时把这两个参数带过去:
navigation.navigate('SummonerRecord', { summonerName: name.trim(), region: regionCode, });实操中我发现,不强制校验就跳转,经常出现summonerName是空字符串或者带了空格导致接口404的情况。后来我在导航跳转前统一走一个参数校验函数,不合法就toast提示,不执行navigate。这个校验逻辑虽然只是业务层的几行代码,但让导航的稳定性提升了一个档次——因为崩溃往往不是导航框架的问题,而是业务给导航喂了脏数据。
6. 实机调试踩掉的坑:不是每个问题都写在文档里
6.1 切Tab动画掉帧:先关掉动画再查原因
真机上第一次跑通主导航,我切Tab时明显感觉到掉帧,动画不顺滑,像抽风一样。按我的经验,第一反应不是去优化原生渲染,而是先确认动画在当前适配层的能力边界。
果不其然,切Tab的掉帧来自React Navigation的底Tab转场动画在OpenHarmony适配层上走JS驱动,没有原生动画模块加速。我把animation相关的配置关掉,改成几乎无感的淡入淡出:
screenOptions={{ transitionSpec: { open: { animation: 'timing', config: { duration: 120 } }, close: { animation: 'timing', config: { duration: 120 } }, }, }}这之后切Tab的体感流畅很多。这个坑说明一个道理:跨端适配初期,不要追求动画效果的花哨,先让交互"不难受"再谈"好看"。
6.2 图片和字体的渲染差异:从资源管理到自定义字体
图片资源在OpenHarmony上有一个和Android完全不同的点:资源索引方式不同。RN里require('../assets/icon.png')这种写法本身没问题,适配层会把资源打包进原生工程的resource目录,但动态加载的图片——比如赛事封面、英雄头像这类从接口返回的URL图片——fresco这个图片加载库在OpenHarmony上的适配是渐近完善的,我遇到的问题是内存缓存不及时回收,切Tab多了之后页面内存明显涨。
我的处理是:列表里的远程图片统一走一个FastImage的封装层,在它内部设置合理的缓存策略和占位图。这个封装层不改变业务组件的调用方式,只是未来适配层完善时可以无缝替换。
中文渲染也是个隐藏问题。OpenHarmony默认字体和Android对中文的字体度量不一样,同样的fontSize: 14在某些手机会截断。我在全局样式里统一设置了字体族为系统默认,并给数字、英文单独指定了字重,这样在模拟器和真机上表现能保持一致。
6.3 系统返回键与导航栈的联动
这是一个必踩的坑。在Android上,RN的NavigationContainer会自动处理系统返回键,默认行为是返回栈顶。但在OpenHarmony上,系统返回键事件不会自动传给RN的导航容器,需要自己在原生侧的页面生命周期里把返回事件桥接给RN。
我的处理方式是在ArkTS入口处监听系统返回事件,并通过原生事件通道回调给JS层,JS层再调用导航的goBack或popToTop逻辑。具体代码不贴全,但思路是:
- ArkTS侧监听
onBackPressed事件 - 把事件交给RN的
DeviceEventEmitter或自定义事件桥 - JS侧注册一个返回键处理Handler
- 判断当前导航栈是否可以回退,可以就回退,不可以就交给系统兜底
这个坑不解决,App在真机上会表现得很诡异:用户按系统返回键没反应,或者直接退出了App而不是返回上一页。
6.4 真机热更新连不上:网络白名单与DevServer
开发时连Metro热更新,真机总是报"disconnected"或者根本拉不到Bundle。排查后的根因是:OpenHarmony工程在module.json5里默认没有放开对局域网HTTP的访问权限。
我要做的不是去改系统权限,而是在module.json5里正确配置网络权限和明文传输信任:
{ "module": { "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ] } }同时Metro启动时要指定--host为宿主机局域网IP,不能是localhost,否则设备连不上。这是我调了一个下午才想明白的事,但解决了之后整个开发效率就上来了。顺带一提,调试模式下每次改代码都会增量同步Bundle,调UI布局的速度比原生ArkTS要快不少,这是RN跨端开发在OpenHarmony上仍然保留的最大优势。
6.5 SafeArea与状态栏的适配
最后一个坑是安全区适配。OpenHarmony设备大多有刘海屏或者挖孔屏,RN默认的SafeAreaView在适配层上的表现不稳定,有时候底部会顶到导航条,有时候状态栏会把页面标题遮住。
我直接在根NavigationContainer外面包了一层自研的RootSafeArea组件,它通过原生模块读取设备安全区参数,给页面根容器统一设置paddingTop和paddingBottom。这个组件对每个Tab栈里的页面都生效,因为所有页面都挂在主容器下。注意不要在每个页面里单独处理SafeArea,那样会出现页面切换过程里安全区高度跳动的问题。
7. 主导航跑通之后的几点个人判断
把主导航完整跑通之后,我对"RN for OpenHarmony"这个组合的结论是:适合,但有条件。适合的是工具类、内容类、助手类App,它们的页面形态规律、交互深度有限、对动画性能的要求是"能稳住就行";有条件的是团队必须接受适配层的不完美,并愿意自己封装一些兼容逻辑。
回到这个英雄联盟助手原型上,现在打开App,冷启动之后进入资讯列表,切到英雄库可以一路翻到出装页,赛事Tab的三段切换和赛程详情都稳定工作,参数传递被类型约束得一清二楚,整个主导航体验已经达到了可以给身边人演示的程度。这个阶段的项目,最大的价值已经不是功能多少,而是验证了"用RN在OpenHarmony上做一个真实App"这条路是走得通的。
如果后面继续往里填内容,我会优先做两个方向:一是把react-native-screens在OpenHarmony上的原生适配彻底跑通,把导航栈切到native实现,解决长列表页面栈内存占用的问题;另一个是把英雄详情的富文本和图片展示做成一个高性能的载体,因为游戏助手类App的核心价值在内容密度,导航只是骨架,内容渲染才是血肉。骨架已经立住了,血肉的问题留给下一轮迭代去解决。