《鸿蒙原生应用开发实战》第四篇:多页面导航与参数传递实战
《鸿蒙原生应用开发实战》第四篇多页面导航与参数传递实战前言一个真正的应用不可能只有一个页面。页面之间的跳转、参数传递、数据回传是开发中频率最高的操作。HarmonyOS 提供了强大的router路由模块来管理页面导航。在本篇中我们将结合「光遇·心境」应用的 5 个页面深入讲解router 路由核心 API页面间参数传递的多种方式页面生命周期与数据加载时机路由栈管理与返回处理参数类型安全与错误处理一、路由系统概述路由表配置所有页面必须在main_pages.json中注册{src:[pages/Index,pages/ScenePage,pages/DetailPage,pages/FavPage,pages/ProfilePage]}注册时只写路径不含.ets后缀路径相对于ets/目录。5 个页面组成了一个完整的导航体系┌─────────────┐ │ Index │ ← 首页入口 └──────┬──────┘ ┌────────────────┼────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌───────────┐ ┌───────────┐ │ ScenePage│ │ FavPage │ │ProfilePage│ └─────┬────┘ └───────────┘ └───────────┘ ▼ ┌───────────┐ │DetailPage │ ← 详情页可来自多个入口 └───────────┘二、router 核心 API导入方式API 23 强制要求从ohos.router导入// ✅ 正确 — API 23 支持的方式importrouterfromohos.router;// ❌ 错误 — API 23 不导出此路径// import { router } from kit.AbilityKit;页面跳转// 基本跳转无参数router.pushUrl({url:pages/FavPage});// 带参数跳转router.pushUrl({url:pages/DetailPage,params:{sceneId:1}});// 多个参数router.pushUrl({url:pages/ScenePage,params:{category:海洋,from:homepage}});页面返回// 简单返回上一页router.back();// 返回并携带数据回传router.back({url:pages/Index,params:{result:fromDetail}});获取参数// 在目标页面的 aboutToAppear 中获取参数aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[sceneId]){constidparams[sceneId]asnumber;this.scenegetSceneById(id);}}三、参数传递实战案例案例1首页 → 场景列表分类筛选首页的分类入口点击后将分类名称传递给 ScenePage// Index.ets — 分类入口点击CategoryItem(name:string){Column(){Text(this.categoryIcons[name]||✨).fontSize(28)Text(name).fontSize($r(app.float.caption_font_size))}.onClick((){router.pushUrl({url:pages/ScenePage,params:{category:name}// 传递晨光/森林等});})}在 ScenePage 接收并使用参数// ScenePage.etsStateselectedCategory:string全部;Statescenes:SceneItem[]getScenes();aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[category]){constcatparams[category]asstring;this.selectedCategorycat;this.scenesgetScenesByCategory(cat);// 根据分类筛选}}效果从首页点击海洋分类 → 直接跳转到 ScenePage 并展示所有海洋场景。案例2多个入口 → 详情页DetailPage 可以从 3 个不同入口进入// 入口1首页的每日推荐卡片// Index.etsrouter.pushUrl({url:pages/DetailPage,params:{sceneId:this.dailyScene.id}});// 入口2场景列表的卡片// ScenePage.etsrouter.pushUrl({url:pages/DetailPage,params:{sceneId:item.id}});// 入口3收藏列表的卡片// FavPage.etsrouter.pushUrl({url:pages/DetailPage,params:{sceneId:item.id}});详情页统一接收// DetailPage.etsStatescene:SceneItem|undefinedundefined;aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[sceneId]){constidparams[sceneId]asnumber;this.scenegetSceneById(id);if(this.scene){this.checkFavStatus();}}}设计原则DetailPage 不关心从哪个入口来的它只读取sceneId参数。这种松耦合的设计让代码更容易维护。四、页面生命周期与数据加载页面生命周期方法方法触发时机适用场景aboutToAppear()页面即将显示每次进入加载数据、读取参数onPageShow()页面每次显示时刷新数据从其他页面返回时onPageHide()页面隐藏时暂停动画/音视频aboutToDisappear()页面销毁前释放资源aboutToAppear vs onPageShow 的区别// 场景收藏页 → 详情页 → 点击收藏 → 返回收藏页// aboutToAppear() — 只在新创建页面时调用// 第一次进入 FavPage: ✅ 调用// 从 DetailPage 返回 FavPage: ❌ 不调用页面还在栈中// onPageShow() — 每次页面显示时都调用// 第一次进入 FavPage: ✅ 调用// 从 DetailPage 返回 FavPage: ✅ 调用这就是为什么我们在 FavPage 中使用aboutToAppear而不是onPageShow—— 因为收藏列表在返回时需要自动刷新我们需要在每次显示时重新加载数据Componentstruct FavPage{StatefavScenes:SceneItem[][];aboutToAppear():void{this.loadFavScenes();// 每次进入页面时刷新收藏列表}loadFavScenes():void{constfavIds:number[]AppStorage.getnumber[](FAV_KEY)||[];constlist:SceneItem[][];for(constidoffavIds){constscenegetSceneById(id);if(scene){list.push(scene);}}this.favSceneslist;this.favCountlist.length;}}性能建议aboutToAppear中不要做耗时操作如网络请求如果需要异步加载数据使用aboutToAppear发起请求用State监听结果频繁刷新的数据用onPageShow一次性初始化用aboutToAppear五、路由栈管理路由栈的工作机制router 内部维护了一个页面栈LIFO初始状态: [Index] push FavPage: [Index, FavPage] push DetailPage: [Index, FavPage, DetailPage] back(): [Index, FavPage] ← DetailPage 出栈 back(): [Index] ← FavPage 出栈router.pushUrl 与 router.replaceUrlAPI行为路由栈pushUrl压入新页面保留当前页面栈增长replaceUrl替换当前页面不保留栈不变back()弹出栈顶页面栈缩小clear()清空栈空栈replaceUrl的典型使用场景是登录页 → 首页// 登录成功后替换用户无法返回到登录页router.replaceUrl({url:pages/Index});避免路由栈过深理论上路由栈可以无限增长但过深的栈会消耗内存。建议不用的页面及时back()使用replaceUrl替代某些场景的pushUrl监听页面栈数量六、参数传递的高级话题参数类型安全由于router.getParams()返回的是Object类型需要进行类型断言// 安全获取参数的模式aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object|undefined;// 使用可选链和类型守卫constsceneIdparams?.[sceneId];if(typeofsceneIdnumber){this.scenegetSceneById(sceneId);}// 或者使用断言 兜底constcat(params?.[category]asstring)||全部;this.selectedCategorycat;}可以传递的数据类型类型示例是否支持string{ name: hello }✅number{ id: 42 }✅boolean{ isAdmin: true }✅Object{ user: { name: Tom } }✅Array{ ids: [1,2,3] }✅Function{ cb: (){} }❌ 不支持复杂嵌套对象含多层嵌套✅ 会被序列化参数的大小限制路由参数不要传大数据超过 10KB 的数据建议用 AppStorage 或全局变量共享因为参数在序列化/反序列化过程中有性能开销。七、完整的导航流程图┌─────────────────────────────────────────────────────────────┐ │ 用户操作流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 打开App → Index首页 │ │ ├── 点击每日推荐 → push({sceneId}) → DetailPage │ │ │ └── 点击收藏 ❤️ → AppStorage 更新 → back │ │ │ │ │ ├── 点击晨光分类 → push({category: 晨光}) → ScenePage│ │ │ └── 点击场景卡片 → push({sceneId}) → DetailPage │ │ │ └── back → back │ │ │ │ │ ├── 点击❤️按钮 → push({}) → FavPage │ │ │ ├── 点击卡片 → push({sceneId}) → DetailPage │ │ │ └── 点击取消收藏 → AppStorage 更新 │ │ │ │ │ └── 点击按钮 → push({}) → ProfilePage │ │ └── back │ │ │ │ 任意页面 ← back 返回上一页 │ │ │ └─────────────────────────────────────────────────────────────┘八、常见踩坑记录坑1使用 kit.AbilityKit 的 router现象编译通过但运行时 crash原因API 23 不导出kit.AbilityKit中的 router解决统一使用import router from ohos.router坑2参数接收不到现象router.getParams()返回 undefined原因在aboutToAppear之前调用或页面未通过 router 进入解决在aboutToAppear生命周期中获取参数坑3返回时数据没有刷新现象从详情页取消收藏返回收藏页列表没有变化原因FavPage 在aboutToAppear中加载数据但页面在栈中未被销毁不会调用aboutToAppear解决使用onPageShow()代替或在 AppStorage 上注册监听九、路由与状态管理的协作模式// 推荐的协作模式// 1. 路由负责页面导航和轻量参数router.pushUrl({url:pages/DetailPage,params:{sceneId:1}// 只传递 ID不传递完整数据});// 2. 共享数据通过 AppStorage 管理AppStorage.setnumber[](FAV_KEY,favList);// 3. 页面从数据仓库获取完整数据constscenegetSceneById(sceneId);// 通过 ID 获取这种设计的好处参数简洁只传 ID 不传整个对象数据源统一避免多页面数据不一致调试方便数据流向清晰总结本篇我们深入学习了✅ router 路由核心 API 与导入规范✅ 4 种参数传递实战场景✅ 页面生命周期与数据加载时机✅ 路由栈管理与返回机制✅ 参数类型安全与错误处理✅ 路由与状态管理的协作模式路由是连接页面的桥梁掌握好这一章多页面应用开发就会得心应手。最后一篇将聚焦完整的收藏功能闭环与应用发布准备。下一篇预告收藏功能、资源管理与构建发布 —— 从功能闭环到 HAP 包发布全流程