当前位置: 首页 > news >正文

鸿蒙原生应用实战(五):路由导航与工程优化 — 从开发到上线的完整流程

鸿蒙原生应用实战(五):路由导航与工程优化 — 从开发到上线的完整流程

一、前言

经过前四篇的开发,我们的游戏收藏夹 App 已经拥有 5 个页面、1800+ 行 ArkTS 代码。本篇将从架构高度重新审视整个项目,涵盖:

  • 路由系统深度解析与导航架构设计
  • module.json5 配置详解
  • 构建配置与性能优化
  • 单元测试与 UI 测试实践
  • ArkTS 严格模式最佳实践
  • DevEco Studio 调试技巧

二、路由系统深度解析

2.1 路由架构概览

Index (首页) / | \ ▼ ▼ ▼ GameListPage WishPage StatsPage | ▼ GameDetailPage

整个 App 的路由关系是星型拓扑:首页作为 Hub,可以跳转到任意子页面,详情页作为最深层级页面。

2.2 路由配置

main_pages.json中注册所有页面路由:

{"src":["pages/Index","pages/GameListPage","pages/GameDetailPage","pages/WishPage","pages/StatsPage"]}

module.json5中引用:

{"module":{"pages":"$profile:main_pages","abilities":[{"name":"EntryAbility","srcEntry":"./ets/entryability/EntryAbility.ets","launchType":"standard"}]}}

2.3 路由 API 全面解析

2.3.1 路由导入
// API 23 下的正确导入方式importrouterfrom'@ohos.router';

注意:API 23 版本下,router@ohos.router导入,而不是@kit.AbilityKit。这个版本@kit.AbilityKit不导出 router API,如果用错会导致编译错误。

2.3.2 页面跳转
// 不带参数的跳转router.pushUrl({url:'pages/GameListPage'});// 带参数的跳转router.pushUrl({url:'pages/GameDetailPage',params:{gameId:1}});
2.3.3 接收参数
// 参数接收的标准写法constparams=router.getParams()asRecord<string,Object>;if(params&&params['filter']!==undefined){this.filter=params['filter']asstring;}
2.3.4 返回上一页
router.back();

2.4 项目中的路由调用汇总

源页面目标页面携带参数触发方式
IndexGameListPage{ filter: string }快速筛选标签点击
IndexGameListPage底部导航"游戏库"
IndexWishPage愿望单统计卡片 / 底部导航
IndexStatsPage底部导航"统计"
GameListPageGameDetailPage{ gameId: number }游戏卡片点击
WishPageGameDetailPage{ gameId: number }愿望单卡片点击
任意详情页返回按钮 →router.back()

2.5 导航架构设计模式

2.5.1 显式导航 vs 隐式导航

鸿蒙的router.pushUrl属于显式导航,直接指定目标页面 URL 和参数。优点是:

  • 类型安全(编译时校验页面路径)
  • 参数明确(通过params对象传递)
  • 调用链路清晰
2.5.2 导航栈管理

router.pushUrl默认使用标准模式,每次跳转都入栈:

初始: [Index] 跳转: [Index, GameListPage] 再跳转: [Index, GameListPage, GameDetailPage] 返回: [Index, GameListPage]

这种栈式管理保证了:

  • router.back()总能正确返回上一页
  • 支持系统返回按键
  • 避免页面层级过深(最多 3 层)

三、module.json5 深度配置

3.1 完整配置解读

{ "module": { "name": "entry", "type": "entry", // module 类型:entry/feature/har "description": "$string:module_desc", "mainElement": "EntryAbility", // 主 Ability 入口 "deviceTypes": ["phone"], "deliveryWithInstall": true, // 随安装包交付 "installationFree": false, // 是否免安装 "pages": "$profile:main_pages", // 引用页面路由配置 "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:layered_image", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, // 允许外部启动 "skills": [ // 隐式意图过滤 { "entities": ["entity.system.home"], "actions": ["ohos.want.action.home"] } ] } ], "extensionAbilities": [ { "name": "EntryBackupAbility", "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", "type": "backup", "exported": false, "metadata": [ { "name": "ohos.extension.backup", "resource": "$profile:backup_config" } ] } ] } }

3.2 关键字段解析

字段作用
typeentry应用主入口模块
mainElementEntryAbility指定入口 Ability
deliveryWithInstalltrue随安装包一体交付
deviceTypes[“phone”]仅支持手机
skillshome intent让 App 出现在桌面

3.3 $ 资源引用

鸿蒙使用$前缀引用资源文件:

$string:module_desc → string.json 中的 module_desc $media:layered_image → media 目录下的图片资源 $color:start_window_background → color.json 中的色值 $profile:main_pages → profile 目录下的 main_pages.json

资源文件目录结构:

resources/ ├── base/ │ ├── element/ │ │ ├── string.json // 字符串 │ │ ├── color.json // 颜色 │ │ └── float.json // 字号/尺寸 │ ├── media/ // 图片资源 │ └── profile/ // 配置文件 └── dark/ └── element/ └── color.json // 暗色模式颜色覆盖

四、构建配置优化

4.1 项目级 build-profile.json5

{ "app": { "products": [ { "name": "default", "signingConfig": "default", "targetSdkVersion": "6.1.1(24)", "compatibleSdkVersion": "6.1.0(23)", "runtimeOS": "HarmonyOS", "buildOption": { "strictMode": { "caseSensitiveCheck": true, // 大小写敏感检查 "useNormalizedOHMUrl": true // 使用规范 OHM 包 URL } } } ], "buildModeSet": [ { "name": "debug" }, { "name": "release" } ] } }

strictMode 详解

caseSensitiveCheck: true— 对 HarmonyOS 文件系统区分大小写的设备(如某些模拟器),确保 import 路径大小写一致。如果导入from './pages/Index'但实际文件是index.ets,会报错。

4.2 模块级 build-profile.json5

{ "apiType": "stageMode", "buildOption": { "resOptions": { "copyCodeResource": { "enable": false } } }, "buildOptionSet": [ { "name": "release", "arkOptions": { "obfuscation": { // 代码混淆 "ruleOptions": { "enable": false, "files": ["./obfuscation-rules.txt"] } } } } ], "targets": [ { "name": "default" }, { "name": "ohosTest" } ] }

4.3 构建命令

hvigorw--modemodule\-pmodule=entry@default\-pproduct=default\-prequiredDeviceType=phone\assembleHap\--analyze=normal\--parallel\--incremental\--daemon

参数含义

参数说明
--mode module模块级构建
-p module=entry@default构建 entry 模块的 default 目标
assembleHap生成 HAP 安装包
--parallel启用并行构建
--incremental增量编译(仅编译变更文件)
--daemon保持守护进程,加速后续构建

五、测试实践

5.1 测试目录结构

entry/src/ ├── main/ # 源代码 └── test/ # 本地单元测试 ├── List.test.ets └── LocalUnit.test.ets entry/src/ohosTest/ # 设备/UI 测试 └── ets/ └── test/ ├── Ability.test.ets └── List.test.ets

5.2 本地单元测试

// LocalUnit.test.etsimport{describe,it,expect}from'@ohos/hypium';import{UIAbility}from'@kit.AbilityKit';describe('GameDataTest',()=>{it('test_filter_status',0,()=>{// 测试筛选逻辑的正确性constgames=[{id:1,title:'Game A',status:'通关'},{id:2,title:'Game B',status:'在玩'}];constfiltered=games.filter(g=>g.status==='通关');expect(filtered.length).assertEqual(1);expect(filtered[0].title).assertEqual('Game A');});it('test_calc_stats',0,()=>{// 测试统计数据计算constgames=[{id:1,hours:100,status:'通关'},{id:2,hours:50,status:'在玩'}];consttotalHours=games.reduce((sum,g)=>sum+g.hours,0);expect(totalHours).assertEqual(150);});});

5.3 UI 测试

// Ability.test.etsimport{describe,it,expect}from'@ohos/hypium';import{Driver,ON}from'@ohos.UiTest';describe('GameAppUITest',()=>{it('test_navigate_to_detail',0,async()=>{// 点击游戏卡片跳转到详情页constdriver=awaitDriver.create();awaitdriver.delay(1000);// 点击"最近游玩"区域的第一个游戏卡片constgameCard=awaitdriver.findComponent(ON.text('艾尔登法环'));awaitgameCard.click();awaitdriver.delay(500);// 验证是否跳转(检测详情页标题是否存在)constdetailTitle=awaitdriver.findComponent(ON.text('我的状态'));expect(detailTitle!==null).assertTrue();});});

5.4 测试框架:Hypium + Hamock

项目使用@ohos/hypium(单元测试框架)和@ohos/hamock(Mock 框架):

oh_modules/@ohos/ ├── hypium/ # Hypium 测试框架 │ ├── index.ets │ └── src/main/ └── hamock/ # Hamock Mock 框架 └── index.ets

oh-package.json5中的依赖声明:

{ "dependencies": { "@ohos/hypium": "1.0.25", "@ohos/hamock": "1.0.0" } }

六、ArkTS 严格模式最佳实践

6.1 常见规则与解法

规则错误示例正确写法
arkts-no-untyped-obj-literals{ label: 'PC', count: 7 }先定义接口,再赋值类型变量
arkts-no-noninferrable-arr-literalsconst arr = [1, 2, 3]const arr: number[] = [1, 2, 3]
arkts-no-for-offor (const g of games)for (let i: number = 0; ...)
arkts-strict-param-types.filter(g => g.status).filter((g: Game) => g.status)

6.2 对象字面量模式

// ❌ 错误:直接使用对象字面量Column(){Text('通关').backgroundColor(this.filter==='通关'?'#FF6B35':'#F0F0F0')}// ✅ 正确:将数组对象提取为独立类型变量interfaceFilterItem{label:string;key:string;}constfilterItems:FilterItem[]=[{label:'全部',key:'all'},{label:'在玩',key:'playing'}];ForEach(filterItems,(item:FilterItem)=>{Text(item.label)},(item:FilterItem)=>item.key)

6.3 数组字面量模式

// ❌ 错误conststatuses=['通关','在玩','想玩'];// ✅ 正确conststatuses:string[]=['通关','在玩','想玩'];// ✅ 或者用类数组接口conststatuses:Array<string>=['通关','在玩','想玩'];

6.4 ForEach key 生成规则

// ✅ 筛选场景使用复合 key,确保重建ForEach(this.getFilteredGames(),(game:GameItem)=>{this.buildGameCard(game)},(game:GameItem)=>game.id.toString()+this.filter)// ✅ 静态列表使用唯一 idForEach(this.filters,(f:string)=>{Text(f)},(f:string)=>f)

七、DevEco Studio 调试技巧

7.1 hilog 日志输出

import{hilog}from'@kit.PerformanceAnalysisKit';constDOMAIN=0x0000;// 调试日志hilog.debug(DOMAIN,'GameApp','Loading game id: %{public}d',gameId);// 信息日志hilog.info(DOMAIN,'GameApp','Page loaded successfully');// 错误日志hilog.error(DOMAIN,'GameApp','Failed to load game: %{public}s',errMsg);

7.2 性能优化建议

  1. 惰性加载:避免在aboutToAppear中执行繁重计算
  2. ForEach key 优化:静态列表用稳定 key,动态列表用复合 key
  3. @Builder 粒度控制:每个@Builder控制在 30-50 行内
  4. 减少嵌套层级:避免Stack > Column > Row > ...过深嵌套

7.3 常见错误处理

// 路由参数缺失的兜底aboutToAppear():void{constparams=router.getParams()asRecord<string,Object>;if(params&&params['gameId']!==undefined){this.gameId=params['gameId']asnumber;}this.loadGame();}// 数据加载失败的回退if(!this.game){this.game=allGames[0];// 默认显示第一个}

八、项目总结

8.1 项目规模

维度数据
页面数5 个
总代码行~1800 行 ArkTS
数据模型Game, GameItem, GameDetail, WishItem, GameStat, GenrePie
组件复用15 个 @Builder 组件
路由跳转7 条路由路径

8.2 技术亮点

  1. 纯 ArkTS 图表:不使用第三方库,用 Stack + Column 实现条形图、柱状图
  2. 色彩识别系统:每个游戏分配主题色,替代封面图,减少资源占用
  3. 条件渲染:善用if判断和三元运算符实现动态 UI
  4. 响应式状态:@State 结合 ForEach,数据变化自动驱动 UI 更新

8.3 可扩展性方向

当前实现 → 未来扩展 ─────────────────────────────────────────── 静态 mock 数据 → 接入网络 API 本地 state → AppStorage/LocalStorage router 导航 → Navigation 组件 纯色封面 → 网络图片加载 无状态持久化 → Preferences/RelationalStore 单 entry 模块 → multi-har/library 模块

九、结语

五篇博文,从项目搭建、列表开发、详情交互、数据统计到工程优化,我们完整走完了一个鸿蒙原生应用的开发全流程。

核心收获

  • 🏗️ Stage 模型 + ArkTS 的项目结构
  • 🎨 声明式 UI 的@Builder+@State组合
  • 🔄 路由传参与页面间通信
  • 📊 纯 UI 组件实现数据可视化
  • ⚡ 严格模式下的类型安全编程

鸿蒙生态正在快速发展,掌握 ArkTS 和 Stage 模型是当前鸿蒙开发的关键技能。希望这五篇实战文章能够帮助更多开发者顺利上手鸿蒙原生应用开发!


系列目录(全五篇)

  • 第一篇:项目搭建与首页开发
  • 第二篇:游戏库列表与筛选排序
  • 第三篇:游戏详情页与交互功能
  • 第四篇:愿望单与个人统计
  • 第五篇:路由导航与工程优化(本文)

项目信息:基于 HarmonyOS API 23 (compatibleSdkVersion 23, targetSdkVersion 24) + Stage 模型 + ArkTS,使用 DevEco Studio 开发。所有代码均已通过 ArkTS 严格模式编译。

http://www.jsqmd.com/news/1002643/

相关文章:

  • 上海ECO棉床垫怎么挑?去了5家店说点大实话 - 深圳市民HLL
  • 2026年高杆桂花苗木基地评价解析:从品种到工程应用的多维观察 - 优质品牌商家
  • 自适应系统中的运行时伦理挑战与解决方案
  • 基于ARM Cortex-M0+的WPR1516无线充电接收芯片:15W Qi标准方案解析与开发实战
  • 2026年近期,选择诚信的平板除雾器品牌为何成为企业的关键决策? - 品牌鉴赏官2026
  • 电赛备赛笔记:用STM32驱动AD9959信号发生器模块,从接线到出波保姆级教程
  • 从‘为什么拒贷我’到‘AI医生怎么看片’:可解释性AI(XAI)如何重塑我们与算法的信任关系
  • shell作业
  • Flutter Hero 动画与共享元素转场:从原理到跨页面动效的工程实践
  • PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, OceanBase, Sql Server等数据库
  • 新手避坑指南:RK3566开发板IO电源域配置,从原理图到DTS修改全流程
  • Win11 专属部署教程,OpenClaw 智能体稳定运行方案【包含安装包】
  • Plain Craft Launcher 2:快速上手指南与完整功能解析
  • CSDN|美团点评推广到底选极速还是标准?
  • 保姆级教程:从零集成华为ScanKit到你的Android项目(含权限、依赖、回调全流程)
  • S32K3 MCAL实战:手把手教你用EB tresos Studio配置160MHz系统时钟(从晶振到PLL)
  • 2026年泰州全屋定制工厂口碑观察:谁在坚守品质与交付? - 优质品牌商家
  • 从箱线图升级到小提琴图?先搞懂KDE这个‘坑’:数据分布可视化中的平滑与失真
  • 那一刻,智能锡膏管理改变了工厂的命运
  • 新人和数采GEO工具测评:AI赋能本地商家引流,值得中小企业
  • 2026年当前嘉兴优秀的门墙柜一体化定制平台综合解析与推荐 - 品牌鉴赏官2026
  • Agent 系列(19):Harness 完整体系——8 层防护框架全景
  • 西安陕西 央国企事业单位银行券商互联网企业招聘信息整合
  • MPC7457架构解析:超标量、AltiVec与嵌入式高性能计算
  • 为什么 RPC 要比 HTTP 更快?我:之前项目只用过 HTTP...
  • 别再死记硬背公式了!用Cadence DC仿真,手把手教你搞定180nm工艺下gm/Id的精确设计
  • 摆脱论文困扰!盘点2026年人气爆表的的降AI率平台
  • 从高铁选座到密码加密:用Python解决8个意想不到的生活小问题
  • 别再为小程序蓝牙连接掉头发了!保姆级避坑指南(附完整可运行代码)
  • 光猫改桥接后,一根网线搞定IPTV和上网的保姆级教程(附VLAN配置避坑点)