写点什么

探索 HarmonyOS:一键掌握 Router 与 NavPathStatck 的传参和页面回调技巧

作者:李小轰
  • 2024-11-29
    北京
  • 本文字数:6536 字

    阅读完需:约 21 分钟

探索HarmonyOS:一键掌握Router与NavPathStatck的传参和页面回调技巧

路由的选择

HarmonyOS 提供两种路由实现的方式,分别是 RouterNavPatchStack。两者使用场景和特效各有优劣。



  • 什么时候使用 NavPatchStack ?


如果是单包应用开发,不使用动态包(hsp)进行拆包,只是使用静态包(har)简单的进行模块拆分,那么我推荐使用 navPatchStack


  • 什么时候使用 Router ?


如果像开发 鸿蒙元服务,对单包体积有 2M 的限制,那么我们不得不使用动态包的方式。将相对独立的功能,二级页面等拆分出去,封装成动态包,可避开 dependencies 直接依赖得引用形式。


此时使用 router 跳转 url 的方式才可跳转到动态包内非直接引用的页面

NavPatchStatck 如何跳转(传参)及页面回调

NavPathStack 是配合 Navigation 一起使用的,Navigation 导航组件做统一的页面跳转管理,它提供了一系列属性方法来设置页面的标题栏、工具栏以及菜单栏的各种展示样式。


如何跳转(传参)及实现页面回调?


//第一步:定义一个用于传参和实现页面回调的模型export interface RouterModel {  params?: Object, // 要传递给跳转页面的参数  popCallback?: (value: Object | undefined) => void // 用于页面回调}
复制代码


//第二步,需要在应用的根页面自行维护 navStack 实例,并传递给根节点 Navigation@Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack()
Navigation(this.pageInfos) { Column() {}}.title('NavIndex').navDestination(this.PageMap)

// 统一管理维护路由跳转@Builder PageMap(name: string, params: RouterModel) { if (name === 'pageOne') { TestNavPathPage({ // TestNavPathPage 就是要跳转的目标页面 routerParams: params }) } else { // 默认空页面 }}
复制代码


/// 任意一个页面获取 navPathStack 调用跳转并传参@Componentexport struct RouterCallbackExample {  @Consume('navPathStack') navPathStack: NavPathStack; 
// NavPatchStack 方式跳转并获取回调 navPathStackJump() { const routerParams: RouterModel = { params: '我是入参 123', //传递到跳转页面的入参 popCallback: (callbackValue) => { // 这里拿到回调结果,注意要判断 callbackValue !== undefine // 这里拿到下面目标页面回传的结果 ‘我是回调的结果 345’ } } this.navPathStack.pushPathByName('pageOne', routerParams) // 'pageOne' 对应上面 'PageMap' 方法内定义的路径名称常量 }
build() { Button('跳转').onClick(() => { this.navPathStackJump() }) }}
复制代码


/// 目标页面接收入参、并返回页面回调@Componentexport struct TestNavPathPage {  @Consume('navPathStack') navPathStack: NavPathStack;  routerParams?: RouterModel  @State receiveParams: string = ''
aboutToAppear(): void { // 接收入参,这里拿到上面传入的 ‘我是入参 123’ let receiveParams = this.routerParams!.params }
build() { NavDestination() { Button('关闭页面并回调结果').onClick(() => { if (this.routerParams?.popCallback !== undefined) { this.routerParams.popCallback('我是回调的结果 345 ') } this.navPathStack.pop() }) }.title('跳转目标页') }}
复制代码

Router 如何跳转(传参)及页面回调

Router 跳转可支持跳转本包内页面以及动态包或者拆包内的页面,


  • url 的定义举例如下:


1. 本地包内,或者直接依赖的静态包内页面,url 定义为 : pages/Page12. 分包内的页面,url 定义为 :@bundle:com.rex.harmony.atomic.service/featureName/ets/pages/Page2
// com.rex.harmony.atomic.service 是我的应用包名// featureName 是跳转页面所在的模块名称,对应 module.json5 里面额 name// ets/pages/Page2 为目标页面在模块内的页面路径,对应 main_pages.json 内的页面路径
复制代码


  • url 跳转


router.pushUrl({ url: '', params: Object })
复制代码


  • 注意: 使用 router + url 进行跳转的目标页面必须使用 @Entry 修饰,且在 main_pages.json 文件内填写对应路径



重点:截止 API11 版本,router 支持传递的 params 传参,不是引用传递,所以在动态包内实际获取到的不是同一个对象,为了实现页面回调,router 我们需要做如下封装:


  • 如何跳转(传参)及实现页面回调?抛砖引玉 ---


  1. 在公共的 har 包内定义 Router 管理类 FastRouter


(在下文扩展中解释单例为什么这么实现)


import { RouterModel } from './model/RouterModel'import { router } from '@kit.ArkUI'
/// 基于 router 库封装,为了实现页面回调export class FastRouter { public readonly routerStack: RouterModel[] = []
/// 跨 hsp 使用这种方式实现单例 public static instance(): FastRouter { const storageKey = 'REX_FAST_ROUTER' if (!AppStorage.has(storageKey)) { AppStorage.setOrCreate(storageKey, new FastRouter()) } return AppStorage.get<FastRouter>(storageKey)! }
/// 获取路由传递的入参 public static get getRouterCurrentParams(): RouterModel | undefined { const stack = FastRouter.instance().routerStack if (stack.length === 0) { return undefined } return stack[stack.length - 1] }
/// push 页面 public static async push(route: RouterModel): Promise<void> { try { await router.pushUrl({ url: route.url, params: route.params }) FastRouter.instance().routerStack.push(route) } catch (_) { console.log('>>>>') } }
/// replace 页面 public static async replace(route: RouterModel): Promise<void> { try { await router.replaceUrl({ url: route.url, params: route.params }) const instance = FastRouter.instance() const list = instance.routerStack if (list.length > 0) { instance.routerStack.splice(instance.routerStack.length - 1, 1, route) } } catch (_) { // 暂无处理 } }
/// 退出栈顶页面 public static async pop(animated?: boolean): Promise<void> { router.back() const routerStack = FastRouter.instance().routerStack routerStack.pop() }}
复制代码


  1. 任一页面使用 FastRouter 进行 url 跳转


// 跳转到 hsp 包(feature_hsp_page)内的 TestHspHomePage 页面const routerParams: RouterModel = {  url: '@bundle:com.rex.harmony.atomic.service/feature_hsp_page/ets/pages/TestHspHomePage',  params: '我是入参 1488',  popCallback: (callbackValue) => {    if (callbackValue !== undefined) {      //这里获取跳转页的回调数据      //接收到下文中目标页面的回调结果:‘我是回调的结果 6100 ’    }  }}FastRouter.push(routerParams)
复制代码


  1. 在目标页面内接收入参并回调结果


@Entry@Componentstruct Index {  routerParams?: RouterModel
aboutToAppear(): void { this.routerParams = FastRouter.getRouterCurrentParams as RouterModel let receiveParams = this.routerParams.params //这里接收入参,也就是上面传递的 ‘我是入参 1488’ }
build() { Button('关闭页面并回调结果').onClick(() => { if (this.routerParams?.popCallback !== undefined) { this.routerParams.popCallback('我是回调的结果 6100 ') } FastRouter.pop() }) }}
复制代码

总结

NavPatchStackRouter 两种路由方式各有优劣,NavPatchStack 方便统一管理,Router 方便解耦,两者没有任何关联,可以一起使用,也可以单独使用。

扩展:动态包、静态包的使用差异

说到动态包(HAR)和静态包(HSP),这里扩展一下两者的区别。



静态包的 module.json5 文件,type 标识为 har


{  "module": {    "name": "静态包模块名称",    "type": "har",    "deviceTypes": [      "default",      "tablet"    ]  }}
复制代码


静态包的 module.json5 文件,type 标识为 shared


{  "module": {    "name": "动态包模块名称",    "type": "shared",    "description": "$string:shared_desc",    "deviceTypes": [      "phone",      "tablet"    ],    "deliveryWithInstall": true,    "installationFree": true,    "pages": "$profile:main_pages"  }}
复制代码


动态包和静态包都可以被直接引用,在 oh-package.json5


{ ...省略  "dependencies": {    "@rex/任意名称": "file:../../base/静态包模块名称",    "@rex/任意名称": "file:../../base/动态包模块名称"  }}
复制代码


重点:


  • 动态包可以不直接依赖( 例如:使用 preload ),跳转动态包内的页面可以通过 router 拼接 url 进行跳转

  • har 中的代码和资源跟随使用方编译,如果有多个使用方,存在多个 hap,也包括使用了 hsp 的场景。该 har 的编译产物中会存在多份相同拷贝。


存在两种情况,如果 harA 和 harB 都依赖 harC,单个 hap 依赖 harA、harB,那么只会存在一份 harA、harB、harC;如果 harA 和 harB 都依赖 harC,有两个 hap,hapA 依赖 harA,hapB 依赖 harB,那么最终会存在一份 harA、harB,两份 harC;


  • hsp 中的代码和资源可以独立编译,运行时在一个进程中代码也只会存在一份


举个例子:


  • 如果应用内没有使用动态包,或者把单例的封装放在动态包里被其他包(静态包或动态包)直接依赖,我们的单例可以这么写:


    class SimgleProvider {      private static _instance?: SimgleProvider
public static instance(): SimgleProvider { if (!SimgleProvider._instance) { SimgleProvider._instance = new SimgleProvider() } return SimgleProvider._instance } }
复制代码


  • 在应用的任意一处获取这个单例都是同一个对象

  • 但如果单例封装放在 har 包内,该 har 包被其他包(静态包和动态包)依赖引用。仍然使用上述代码实现单例,我们在 entry 或者 被 entry 直接依赖的 har 中获取到的 SimgleProvider 单例 和在 hsp 包内获取到的 SimgleProvider 单例 不是指向同一份内存,它会被拷贝两份。


可以简单的总结为:一个应用内,同一个 har 包,如果同时被 har (或者 entry)和 hsp 依赖引用,会被拷贝两份。


  • 如果需要整个应用内,包括 entry、har、hsp 都指向的是同一个单例对象,要么把这个单例封装放在 hsp 内,如果单例封装放在 har 内,需要把上面的单例实现代码改成如下:


class SimgleProvider {  private static _instance?: SimgleProvider
public static instance(): FastRouter { const storageKey = 'SimgleProvider' if (!AppStorage.has(storageKey)) { AppStorage.setOrCreate(storageKey, new SimgleProvider()) } return AppStorage.get<SimgleProvider>(storageKey)! }}
复制代码


  • 借助 AppStorage,实现全应用单例


上述示例 demo 已上传,请参考下方链接


附注(Example)

Demo 示例已上传:


GitHub:https://github.com/liyufengrex/HarmonyAtomicService


GitCode:https://gitcode.com/liyufengrex/HarmonyAtomicService


(基于 API11 开发,支持 NEXT 及以上版本运行)已上传可供参考,包含如下内容:


  • 静态库+动态包+多模块设计

  • 状态管理

  • 统一路由管理(router+navPathStack)

  • 网络请求、Loading、Toast、数据持久化 等工具库封装

  • 自定义组件、自定义弹窗(解耦)

  • EventBus 事件通知

  • 扩展修饰器,实现 节流、防抖、权限申请

  • 动态路由 (navPathStack + 动态 import + WrappedBuilder)

  • UI 动态节点操作 (BuilderNode + NodeController)

  • 折叠屏适配示例

  • 组件工厂示例

  • 组件动态属性设置示例


// 工程目录├──entry                                        // ets代码区│  └──src/main/ets │       ├──entryability  │       │  ├──FoldStatusObserver.ets             // 折叠屏幕变化监听│       │  └──EntryAbility.ets         │       ├──pages  │       │  └──MainPage.ets                       // 首页├──business                                      // 放置静态包的文件夹(业务模块)│  ├──feature_home                               // 放置首页Tab里的一些示例页面                 │  │   └──/src/main/ets/pages      │  │      ├──HomePage.ets                         //首页的第一个Tab                     │  │      ├──BuilderNodeExample.ets               //动态节点操作示例          │  │      ├──CustomDialogExample.ets              //自定义弹窗解耦│  │      ├──EventBusExample.ets                  //消息通知│  │      ├──HttpRequestExample.ets               //网络请求示例│  │      ├──PermissionExample.ets                //使用注解请求权限│  │      ├──RouterCallbackExample.ets            //使用 NavPathStack 与 Route 两种方式实现页面跳转及回调(HSP、HAR)│  │      ├──FixFoldUiExample.ets                 //折叠屏适配示例│  │      ├──ComponentFactoryExample.ets          //组件工厂示例│  │      ├──AttributeModifierExample.ets         //组件动态属性设置示例│  │      └──ThrottleExample.ets                  //使用注解防抖│  ├──feature_setting   │  │   └──/src/main/ets/pages  │  │      ├──SettingPages.ets                     //首页的第二个Tab│  │      └──TestDynamicNavPage.ets               //测试动态路由示例├──features                                       //放置动态包的文件夹│  ├──feature_has_page  │  │   └──/src/main/ets/pages  │  │      ├──TestHspNavPathPage.ets               //测试 NavPath 跳转 HSP 内页面│  │      └──TestHspRouterPage.ets                //测试 Route 跳转 HSP 内页面├──base  │  ├──fast_ui                                     //封装公共UI│  │   ├──/src/main/ets/compnents  │  │   │   ├──FoldStatusContainer.ets             // 折叠屏变化响应组件封装│  │   │   ├──FastLoading.ets                     // loading工具│  │   │   └──FastToast.ets                       // toast工具│  │   └──/src/main/ets/styles                    // 公共样式│  ├──fast_util                                   // 通用工具│  │   ├──/src/main/ets │  │      ├──EventBus.ets                         // 消息通知+监听                     │  │      ├──FastLog.ets                          // 日志打印│  │      ├──FastNavRouter.ets                    // 用于动态路由│  │      ├──FastPermission.ets                   // 请求权限注解器│  │      ├──FastRouter.ets                       // 基于 router 库封装,为了实现页面回调│  │      ├──FastTool.ets                │  │      ├──PreferencesUtil.ets                  // 数据持久化工具│  │      └──ThrottleTool.ets                     // 防抖注解器│  ├──global_constant  │  │├──entry/src/main/resources                   // 应用资源目录 └──module.json5                               // 添加卡片拓展能力                         
复制代码

补充

API version 12 开始,Navigation 支持使用系统路由表的方式进行动态路由。各业务模块(HSP/HAR)中需要独立配置 router_map.json 文件(可参考上述 demo 内TestHspNavPathPage.ets文件)。


具体可参考文档:Navigation系统路由


用户头像

李小轰

关注

有趣的灵魂,拒绝沉默 2022-03-28 加入

目前从事 Android、Flutter、HarmonyOS 开发工作,喜欢尝试各种新玩意儿。重度强迫症(匠心精神),乐于分享!『 OpenHarmony 三方库贡献者 』、『 pub.dev 三方库维护者 』

评论

发布
暂无评论
探索HarmonyOS:一键掌握Router与NavPathStatck的传参和页面回调技巧_HarmonyOS NEXT_李小轰_InfoQ写作社区