《HarmonyOS导航深度解析:从传统路由到声明式导航》

前言

在HarmonyOS应用开发中,高效的页面导航与转场管理是构建流畅用户体验的核心要素。为满足不同场景下的开发需求,HarmonyOS提供了两套互补的路由方案:基于Router模块的标准化页面路由基于Navigation组件的声明式导航系统。传统路由(@ohos.router)通过URL路径映射实现跨页面跳转,适合轻量级场景;而组件导航(Navigation)以ArkUI声明式语法为核心,支持在组件层级进行精细化跳转控制,其特性优势显著。下面就浅聊一下:

一、基于 Router 模块的传统路由

特性与功能

  1. 两种跳转模式

    • router.pushUrl() :将目标页面压入页面栈,保留当前页面状态,可通过返回键或 router.back() 返回原页面,返回后之前的目标页出栈。
    • router.replaceUrl() :用目标页面替换当前页面,销毁当前页面资源,无法返回原页面。
    • 页面栈管理 :最大容量为 32 个页面,超出需调用 router.clear() 清空栈以释放内存router.getLength()获取当前页面栈长度。
  2. 参数传递与接收

    • 通过 params 字段传递参数,目标页面通过 router.getParams() 接收。
    • 示例代码:
ts 复制代码
    // 父页面传递参数
    router.pushUrl({ url: 'pages/Detail', params: { id: 123 } });
    // 子页面接收参数
    @State id: number = router.getParams()['id'];
  1. 路由模式

    1. standard(标准实例模式)
    2. Single(单实例模式)

不同模式的决定了页面是否会创建多个实例:

  1. Standard:无论之前是否添加过,一直添加到页面栈【默认】

    场景: 适用于每次跳转都呈现全新内容或状态的场景,避免数据展示紊乱(数据变化

  2. Single:如果之前加过页面,会使用之前添加的页面【需要添加参数手动修改】

    场景: 适用于那些需要保留页面状态或避免重复创建相同页面的场景(数据不变化

路由模式语法:

Ts 复制代码
   Button('跳转')
    .onClick(() => {
      router.pushUrl({url:'pages/Detail',params:{id:4090} },
      router.RouterMode.Single)  //单实例模式
   })
  1. 页面状态管理
  • 支持获取当前页面栈信息(如索引、路径)和页面生命周期事件(如 onPageShow)。

生命周期时序

基于 Router 模块的代码示例

1.基本页面跳转与参数传递

Ts 复制代码
    // 页面A:跳转到页面B,并传递参数
    import router from '@ohos.router';
    ​
    @Entry
    @Component
    struct PageA {
      build() {
        Column() {
          Button('跳转到PageB')
            .onClick(() => {
              // 使用pushUrl跳转,保留当前页面
              router.pushUrl({
                url: 'pages/PageB',
                params: { userId: '123', userName: 'HarmonyOS' }
              }, router.RouterMode.Standard);
            })
        }
      }
    }
    ​
    // 页面B:接收参数
    @Entry
    @Component
    struct PageB {
      @State userId: string = '';
      @State userName: string = '';
    ​
        //如果是需要参数进行网络请求数据则使用aboutToAppear
      onPageShow() {
        // 通过router.getParams()获取参数
        const params = router.getParams();
        this.userId = params['userId'];
        this.userName = params['userName'];
      }
    ​
      build() {
        Column() {
          Text(`用户ID: ${this.userId}`)
          Text(`用户名: ${this.userName}`)
          Button('返回')
            .onClick(() => {
              router.back(); // 返回上一页
            })
        }
      }
    }
  1. 替换当前页面(无返回栈)
ts 复制代码
    // 使用replaceUrl替换当前页面
    router.replaceUrl({
      url: 'pages/LoginPage' // 登录后销毁原页面,无法返回
    });
  1. 清空页面栈
ts 复制代码
    // 清空所有页面栈,跳转到首页
    router.clear();
    router.pushUrl({ url: 'pages/HomePage' });

定义:

Navigation路由相关的操作都是基于页面栈NavPathStack提供的方法进行,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能

特性与功能

  1. 核心优势

    • 高效性能:参数传递时性能更优,支持组件动态加载。
    • 灵活的路由栈管理 :通过 NavPathStack 管理路由栈,无页面数量限制。
    • 丰富的转场动画 :支持默认转场和自定义动画,如共享元素转场(通过 geometryTransition 实现元素联动)。
  2. 功能扩展

    • 跨模块跳转:支持导入其他模块的页面组件,实现模块解耦。
    • 弹窗路由 :通过 NavDestinationMode.DIALOG 实现模态对话框的路由嵌套。
    • 生命周期管理 :提供 aboutToAppearonAppear 等精细控制页面状态。
  3. 参数传递与接收

    • 通过 NavPathInfo 对象传递参数,目标页面在 onReady 生命周期中接收。

    • 示例代码:

ts 复制代码
  // 发起页传递参数
 let param = { key: 'value' };
this.pageStack.pushDestination(new NavPathInfo('targetPage', param));
 // 目标页接收参数
build() {
  NavDestination().onReady(cxt => {
  this.param = cxt.pathInfo.param;
  });
}
  1. 路由操作

Navigation路由相关的操作都是基于页面栈NavPathStack提供的方法进行,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。

markdown 复制代码
*   页面跳转:使用pushPath或pushPathByName打开一个新页面
*   页面返回:使用pop、popToName或popToIndex返回上一个页面或返回到指定页面
*   页面替换:使用replacePath或replacePathByName替换当前页面
*   页面删除:使用removeByName或removeByIndexes删除指定页面
  1. 基于 Navigation 组件的代码示例

跨模块跳转

  1. 在跳转目标模块的配置文件module.json5添加路由表配置:
ts 复制代码
 {
   "module": {
    "name": "home",
     "type": "shared",
     "description": "$string:shared_desc",
     "deviceTypes": [
     "phone",
     "tablet",
      "2in1"
     ],
    "deliveryWithInstall": true,
     "pages": "$profile:main_pages",
     "routerMap": "$profile:route_map"
     }
   }    
  1. 添加完路由配置文件地址后,需要在工程resources/base/profile中创建route_map.json文件。添加如下配置信息:
ts 复制代码
      {
          "routerMap": [
            {
              "name": "PageOne",
              "pageSourceFile": "src/main/ets/pages/PageOne.ets",
              "buildFunction": "PageOneBuilder",
              "data": {
                "description" : "this is PageOne"
              }
            }
          ]
        }
  1. 在跳转目标页面中,需要配置入口Builder函数,函数名称需要和route_map.json配置文件中的buildFunction保持一致,否则在编译时会报错。
ts 复制代码
   // 跳转页面入口函数
        @Builder
        export function PageOneBuilder() {
         PageOne()
          }
      
         @Component
         struct PageOne {
         pathStack: NavPathStack = new NavPathStack()
 
         build() {
         NavDestination() {
          }
       .title('PageOne')
       .onReady((context: NavDestinationContext) => {
       this.pathStack = context.pathStack
    })
   }
 }

共享元素转场动画

  1. 为需要实现共享元素转场的组件添加geometryTransition属性,参数必须在两个NavDestination之间保持一致。
ts 复制代码
             Image($r('app.media.mm'))
                      .geometryTransition('sharedId')
                      .width(100)
                      .height(100)
         
  1. 将页面路由的操作,放到animateTo动画闭包中,配置对应的动画参数以及关闭系统默认的转场。
ts 复制代码
             Button('跳转到详情页')
                      .onClick(() => {
                        this.getUIContext()?.animateTo({
                          duration: 1000,
                          curve: Curve.EaseIn,
                          iterations: 1,
                          playMode: PlayMode.Alternate,
                          onFinish: () => {
                            console.log('动画结束')
                          }
                        }, () => {
                          this.navStack.pushPathByName('DetailPage', new Object({ id: 123, title: 'HarmonyOS' }), false);
                        })
                      })
  1. 效果实现完整代码
ts 复制代码
        import { DetailPage } from './DetailPage';
        ​
        @Entry
        @Component
        struct Index {
          @Provide('navStack') navStack: NavPathStack = new NavPathStack();
        ​
          // 自定义动画
          pageTransition() {
            // Push入场:从底部滑入
            PageTransitionEnter({ type: RouteType.Push, duration: 1000, curve: Curve.EaseOut })
              .translate({ y: '100%' })
              .scale({ x: 0.8, y: 0.8 })
              .opacity(0.7);
            // Pop退场:向右滑出
            PageTransitionExit({ type: RouteType.Pop, duration: 800, curve: Curve.EaseIn })
              .translate({ x: '100%' })
              .opacity(0);
        ​
            // Push退场:向左滑出
            PageTransitionExit({ type: RouteType.Push, duration: 800 })
              .translate({ x: '-100%' });
        ​
            // Pop入场:从左侧滑入
            PageTransitionEnter({ type: RouteType.Pop, duration: 1000 })
              .slide(SlideEffect.Left);
          }
        ​
          build() {
            Navigation(this.navStack) {
              Column({ space: 20 }) {
                Image($r('app.media.mm'))
                  .geometryTransition('sharedId')
                  .width(100)
                  .height(100)
                Button('跳转到详情页')
                  .onClick(() => {
                    this.getUIContext()?.animateTo({
                      duration: 1000,
                      curve: Curve.EaseIn,
                      iterations: 1,
                      playMode: PlayMode.Alternate,
                      onFinish: () => {
                        console.log('动画结束')
                      }
                    }, () => {
                      this.navStack.pushPathByName('DetailPage', new Object({ id: 123, title: 'HarmonyOS' }), false);
                    }
                    )
                  })
              }
              .width('100%')
              .height('100%')
              .justifyContent(FlexAlign.Center)
            }
            .title('首页')
            .titleMode(NavigationTitleMode.Mini)
            .mode(NavigationMode.Stack)
            .navDestination(this.routeMap)
          }
        ​
          @Builder
          routeMap(name: string, params: object) {
            if (name === 'DetailPage') {
              DetailPage(params) // ✅ 正确传递参数
            }
          }
        }
        ​
        interface PageParams {
          id: string
          title: string
        }
        ​
        @Component
        export struct DetailPage {
          @Consume('navStack') navStack: NavPathStack;
          @State private detailParams: PageParams = { id: '', title: '' };
        ​
          aboutToAppear() {
            const params = this.navStack.getParamByIndex(0) as PageParams;
            if (params) this.detailParams = params;
          }
        ​
          build() {
            NavDestination() {
              Column() {
                Image($r('app.media.mm'))
                  .geometryTransition('sharedId')
                  .width(200)
                  .height(200)
                Text(this.detailParams.title || '详情页')
                  .fontSize(24)
                  .margin({ top: 20 })
                Button('返回')
                  .onClick(() => {
                    this.getUIContext()?.animateTo({
                      duration: 1000,
                      curve: Curve.EaseOut,
                      iterations: 1,
                      playMode: PlayMode.Alternate,
                      onFinish: () => {
                        console.log('动画结束')
                      }
                    }, () => {
                      this.navStack.pop(false)
                    })
                  })
              }
            }
            .title('详情页')
          }
        }

效果图:

弹窗路由模式

NavDestination设置mode为NavDestinationMode.DIALOG弹窗类型,此时整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。

ts 复制代码
import { DialogPage } from './DialogPage'
//主页面
@Entry
@Component
struct Index {
 // 创建一个页面栈对象并传入Navigation
@Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack()
@Builder
 PagesMap(name: string) {
  if (name == 'DialogPage') {
   DialogPage()
  }
 }
   build() {
     //传入对象
     Navigation(this.pageStack) {
     Button('Push DialogPage')
    .margin(20)
    .width('80%')
    .onClick(() => {
  this.pageStack.pushPath({ name: "DialogPage", param: new Object({ id: '123abc' })})
            })
        }
        .mode(NavigationMode.Stack) // 默认为Stack
        .title('Main')
        .navDestination(this.PagesMap ) // 子页面
      }
    }
    //弹框页面
    interface PageParams {
      id: string
    }
    @Entry
    @Component
    export struct DialogPage {
      @Consume('NavPathStack') pageStack: NavPathStack;
      @State msg:PageParams[]=[]
    
 aboutToAppear(){
   this.msg = this.pageStack.getParamByName("DialogPage") as PageParams[]
}
build() {
   NavDestination() {
   Stack({ alignContent: Alignment.Center }) {
      Column() {
       Text(this.msg[0].id)
         .fontSize(20)
         .margin({ bottom: 100 })
       Button("Close").onClick(() => {
         this.pageStack.pop()
          }).width('30%')
        }
        .justifyContent(FlexAlign.Center)
        .backgroundColor(Color.White)
        .borderRadius(10)
        .height('30%')
        .width('80%')
        .height("100%").width('100%')
     }
        .backgroundColor('rgba(0,0,0,0.5)')
        .hideTitleBar(true)
        .mode(NavDestinationMode.DIALOG) //设置为弹框模式
      }
    }

效果图:

适用场景

  • 复杂应用架构:如多模块协作、跨模块页面跳转。
  • 高性能需求场景:如游戏加速(延迟降低 20%)、直播/视频流优化。
  • 动态路由管理:需灵活操作路由栈(如清空历史页面、自定义转场逻辑)。
  • 高级交互设计:如共享元素转场、弹窗内嵌路由。

对比维度 Router 模块 Navigation 组件
易用性 简单易上手,适合基础跳转和传参。 需要理解路由栈管理,适合复杂场景。
功能性 支持基本跳转和页面栈管理,功能较为基础。 支持跨模块跳转、弹窗路由、动态加载组件等高级功能。
性能 参数传递性能一般,适合轻量级应用。 参数传递性能优化,适合高频率或大数据量场景。
路由栈限制 页面栈最大容量为 32,需手动清理。 无数量限制,支持动态管理。
生命周期控制 提供基础生命周期事件(如 onPageShow)。 支持细粒度生命周期(如 aboutToAppear)。
转场动画 仅支持默认转场效果。 支持自定义转场动画和共享元素联动。

四、选择建议

  1. Router 适用场景

    • 快速开发简单应用,如工具类 App、表单提交流程。
    • 需要兼容旧版本 HarmonyOS 的项目。
  2. Navigation 适用场景

    • 大型复杂应用,如电商平台、社交 App。
    • 需要多端适配(手机、平板、智能设备)的场景。
    • 对性能、动画效果或动态路由有较高要求的项目。

五、扩展功能与最佳实践

  1. 自适应场景优化 : HarmonyOS 4.0 的 Navigation 支持智能识别用户场景(如游戏、直播),动态分配网络资源,提升体验。
  2. 路由安全 :可通过 RouterManager.setInterception 拦截非法跳转,或在页面返回时添加弹窗确认逻辑
  3. 迁移建议 :若原有项目基于 Router,可逐步将核心页面改造为 Navigation,利用 customNavContentTransition 适配转场动画。
相关推荐
黄同学real6 分钟前
HTML5 新增的主要标签整理
前端·html·html5
liwulin05067 分钟前
【JAVAFX】实现屏幕指定区域截图,带尺寸显示
服务器·前端·python
沉迷...35 分钟前
tsconfig.json和tsconfig.node.json和tsconfig.app.json有什么区别
前端·vue.js·node.js
每次的天空1 小时前
Android学习总结之自定义view设计模式理解
android·学习·设计模式
码上飞扬2 小时前
Nginx功能全解析:你的高性能Web服务器解决方案
服务器·前端·nginx
samuel9182 小时前
pinia实现数据持久化插件pinia-plugin-persist-uni
前端·vue
谢一歇_fn2 小时前
如何在uni-app中自定义输入框placeholder的样式
前端·javascript·uni-app
ganshenml2 小时前
【Web】如何解决 `npm run dev` 报错 `address already in use 127.0.0.1:9005` 的问题
前端
顽强d石头2 小时前
elementui里的el-tabs的内置样式修改失效?
前端·javascript·elementui
king199901023 小时前
小程序Npm package entry file not found?
前端·npm·node.js