鸿蒙开发实战:router与navigation组件的核心差异及选型指南

鸿蒙开发实战:router 与 navigation 组件的核心差异及选型指南

在鸿蒙应用开发过程中,处理页面跳转和导航结构是绕不开的核心环节。其中,router(路由)和 navigation(导航容器)是最常用的两个工具,但很多刚接触鸿蒙开发的开发者容易混淆二者的用法。其实从设计初衷来看,它们就有着明确的分工:router 更像是 “页面间的传令兵”,专注于跳转逻辑和数据传递;而 navigation 则是 “导航系统的总控制台”,不仅能实现跳转,还自带一套完整的导航 UI 体系。下面结合实际开发经验,从定位、功能、代码实现到场景选择,全方位解析二者的区别。

一、本质定位:各司其职的导航工具

刚开始用这两个组件时,我曾尝试用 router 实现底部标签切换,结果不仅要自己写标签栏样式,还要处理切换时的页面状态保存,耗时又容易出问题。后来才明白,二者的设计理念完全不同,根本不是替代关系。

对比维度router(路由)navigation(导航容器)
核心角色页面跳转的 “逻辑执行者”导航体验的 “完整解决方案”
设计目标轻量灵活,专注跳转逻辑标准化导航,降低 UI 开发成本
与 UI 关系无依赖,纯逻辑层工具强绑定,需作为容器包裹页面内容

简单来说,如果你只需要 “从 A 页面跳到 B 页面”,用 router 就够了;但如果需要 “带标题栏、返回按钮、底部标签的完整导航界面”,navigation 才是更合适的选择。

二、功能特性:从跳转逻辑到 UI 体验的差异

1. 页面跳转与返回机制

router 的跳转逻辑很纯粹,通过pushUrlback两个核心方法就能实现页面的入栈和出栈。比如从首页跳转到详情页,调用router.pushUrl后,页面会被加入路由栈,返回时用router.back弹出栈顶页面即可。但这里要注意,router 不会帮你做任何 UI 层面的东西,返回按钮需要自己用 Button 组件实现,标题栏也得手动用 Row 和 Text 拼接。

而 navigation 自带了一套完整的导航交互。只要把页面内容放进 navigation 容器里,它就会自动生成顶部导航栏,左侧默认带返回按钮,点击就能触发back操作,完全不用手动开发。跳转用pushPath方法,但参数传递会自动关联当前容器的上下文,在复杂页面结构中更不容易出问题。

2. 导航 UI 的实现成本

这是二者最直观的区别。用 router 开发时,所有导航相关的 UI 都要从零开始做。比如要做一个顶部标题栏,得用 Row 组件设置宽度 100%,加 padding,设置背景色,再放 Text 组件当标题;底部标签栏则需要用 Tabs 组件配合 TabContent,还要自己处理选中状态的样式。

navigation 则把这些常用导航 UI 都封装好了。想要顶部导航栏,只需要在初始化时传入title参数;也有menus等功能可以直接使用,省去了大量重复的 UI 代码。

3. 参数传递的实际应用

在参数传递方面,二者的基础用法相似,都支持通过pushUrlparams属性传参,也能通过AppStorage实现跨页面数据共享。但在实际开发中,navigation 的参数传递更贴合容器内的页面逻辑。

比如用 navigation 实现的多标签页,每个标签页内的页面跳转,参数会自动限定在当前标签的路由栈中,不会影响其他标签页的状态;而 router 的参数传递是全局的,如果不注意路由栈的管理,在多模块跳转时容易出现参数混乱的问题。

4. 适用场景的实际划分

根据我多次项目开发的经验,二者的适用场景可以清晰地划分:

router 的适用场景

  • 简单的跨页面跳转,比如设置页里 “关于我们” 的跳转

  • 需要高度自定义导航 UI 的场景,比如带搜索框的特殊标题栏

  • 跨模块的页面跳转,比如从商品列表模块跳到支付模块

navigation 的适用场景

  • 标准的多标签页应用,比如电商 APP 的 “首页 - 分类 - 购物车 - 我的”

  • 需要统一导航风格的页面集群,比如新闻 APP 的文章列表和详情页

  • 后台管理系统的侧边导航 + 顶部标题栏组合界面

三、代码实战:两种组件的实际开发案例

场景 1:商品列表跳转到详情页

用 router 实现跳转

这里我做了一个简单的商品列表页,点击商品卡片跳转到详情页,并传递商品 ID 和名称参数。

商品列表页(Pages/GoodsList.ets)

import router from '@ohos.router';

interface GoodItem {
  id: number
  name: string
  price: number
}

@Entry
@Component
struct GoodsListPage {
  // 模拟商品数据
  @State goodsList: GoodItem[] = [
    { id: 101, name: "鸿蒙开发实战手册", price: 59 },
    { id: 102, name: "ArkUI组件大全", price: 49 }
  ];

  build() {
    Column() {
      // 手动实现顶部导航栏
      Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
        Text("商品列表")
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .padding({ left: 16, top: 12, bottom: 12 })
      .backgroundColor('#ffffff')
      .shadow({ radius: 2, color: '#eeeeee' })

      // 商品列表
      List({ space: 10 }) {
        ForEach(this.goodsList, (item: GoodItem) => {
          ListItem() {
            Row({ space: 15 }) {
              Column({ space: 5 }) {
                Text(item.name)
                  .fontSize(16)
                Text(`¥${item.price}`)
                  .fontSize(14)
                  .fontColor('#ff4400')
              }
              .flexGrow(1)
            }
            .width('100%')
            .padding(12)
            .backgroundColor('#ffffff')
            .borderRadius(8)
            .onClick(() => {
              // 跳转详情页并传递参数
              router.pushUrl({
                url: 'pages/GoodsDetail',
                params: { goodsId: item.id, goodsName: item.name }
              });
            })
          }
        })
      }
      .padding(12)
      .flexGrow(1)
    }
    .backgroundColor('#f5f5f5')
    .width('100%')
    .height('100%')
  }
}

商品详情页(Pages/GoodsDetail.ets)

import router from '@ohos.router';

interface RouterParams {
  goodsId: number
  goodsName: string
}

@Entry
@Component
struct GoodsDetailPage {
  aboutToAppear(): void {
    let data = router.getParams() as RouterParams
    this.goodsId = data.goodsId
    this.goodsName = data.goodsName
  }

  // 接收路由参数
  @State goodsId: number = 0
  @State goodsName: string = '未知商品';

  build() {
    Column() {
      // 手动实现带返回按钮的标题栏
      Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
        Button('←')
          .fontSize(18)
          .backgroundColor('transparent')
          .onClick(() => {
            // 返回上一页
            router.back();
          })
        Text(`${this.goodsName}详情`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .padding({ left: 12, top: 12, bottom: 12 })
      .backgroundColor('#ffffff')
      .shadow({ radius: 2, color: '#eeeeee' })

      // 详情内容
      Column({ space: 20 }) {
        Text(`商品ID:${this.goodsId}`)
          .fontSize(16)
        Text('商品描述:这里是商品的详细介绍内容...')
          .fontSize(14)
          .fontColor('#666666')
      }
      .padding(20)
      .flexGrow(1)
    }
    .backgroundColor('#f5f5f5')
    .width('100%')
    .height('100%')
  }
}
用 navigation 实现

同样的场景,用 navigation 可以省去导航栏的开发,直接聚焦业务内容。

商品列表页(Pages/GoodsList.ets)

export interface Good {
  id: number
  name: string
  price: number
}

@Entry
@Component
struct GoodsListPage {
  @State goodsList: Good[] = [
    { id: 101, name: "鸿蒙开发实战手册", price: 59 },
    { id: 102, name: "ArkUI组件大全", price: 49 }
  ];
  @Provide pathStack: NavPathStack = new NavPathStack()

  build() {
    // 初始化navigation容器,设置标题
    Navigation(this.pathStack) {
      List({ space: 10 }) {
        ForEach(this.goodsList, (item: Good) => {
          ListItem() {
            Row({ space: 15 }) {
              Column({ space: 5 }) {
                Text(item.name)
                  .fontSize(16)
                Text(`¥${item.price}`)
                  .fontSize(14)
                  .fontColor('#ff4400')
              }
              .flexGrow(1)
            }
            .width('100%')
            .padding(12)
            .backgroundColor('#ffffff')
            .borderRadius(8)
            .onClick(() => {
              // 跳转详情页
              this.pathStack.pushPath({ name: 'Detail', param: item })
            })
          }
        })
      }
      .padding(12)
    }
    // 设置导航栏样式
    .backgroundColor('#f5f5f5')
  }
}

商品详情页(Pages/GoodsDetail.ets)

import router from '@ohos.router';

// import { Good } from './Index'

interface Good {
  id: number
  name: string
}

@Builder
function DetailBuilder() {
  NavDestination() {
    GoodsDetailPage()
  }
  .hideTitleBar(true)
}

@Entry
@Component
struct GoodsDetailPage {
  @Consume pathStack: NavPathStack

  aboutToAppear(): void {
    let data: Good[] = this.pathStack.getParamByName('Detail') as Good[]
    this.goodsId = data[0].id
    this.goodsName = data[0].name
  }

  // 接收路由参数
  @State goodsId: number = 0
  @State goodsName: string = '未知商品';

  build() {
    Column() {
      // 手动实现带返回按钮的标题栏
      Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
        Button('←')
          .fontSize(18)
          .backgroundColor('transparent')
          .onClick(() => {
            // 返回上一页
            this.pathStack.pop()
          })
        Text(`${this.goodsName}详情`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .padding({ left: 12, top: 12, bottom: 12 })
      .backgroundColor('#ffffff')
      .shadow({ radius: 2, color: '#eeeeee' })

      // 详情内容
      Column({ space: 20 }) {
        Text(`商品ID:${this.goodsId}`)
          .fontSize(16)
        Text('商品描述:这里是商品的详细介绍内容...')
          .fontSize(14)
          .fontColor('#666666')
      }
      .padding(20)
      .flexGrow(1)
    }
    .backgroundColor('#f5f5f5')
    .width('100%')
    .height('100%')
  }
}

场景 2:底部标签栏实现

用 navigation 实现(推荐方案)

底部标签栏是 APP 的常见结构,用 navigation 实现只需几行核心代码:

import { SymbolGlyphModifier } from '@kit.ArkUI';

@Entry
@Component
struct NavigationExample {
  @Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();
  @State menuItems: Array<NavigationMenuItem> = [
    {
      value: 'menuItem1',
      icon: 'resources/base/media/ic_public_ok.svg' // 图标资源路径
    },
    {
      value: 'menuItem2',
      icon: 'resources/base/media/ic_public_ok.svg', // 图标资源路径
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_folder_badge_plus')).fontColor([Color.Red, Color.Green])
        .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_COLOR),
    },
    {
      value: 'menuItem3',
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_lungs')),
    },
  ];
  @State toolItems: Array<ToolbarItem> = [
    {
      value: 'toolItem1',
      icon: 'resources/base/media/ic_public_ok.svg', // 图标资源路径
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_lungs')),
      status: ToolbarItemStatus.ACTIVE,
      activeSymbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_folder_badge_plus')).fontColor([Color.Red,
        Color.Green]).renderingStrategy(SymbolRenderingStrategy.MULTIPLE_COLOR),
      action: () => {
      }
    },
    {
      value: 'toolItem2',
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_star')),
      status: ToolbarItemStatus.ACTIVE,
      activeIcon: 'resources/base/media/ic_public_more.svg', // 图标资源路径
      action: () => {
      }
    },
    {
      value: 'toolItem3',
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_star')),
      status: ToolbarItemStatus.ACTIVE,
      activeSymbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_lungs')),
      action: () => {
      }
    }
  ];

  @Builder
  myRouter(name: string, param?: Object) {
    if (name === 'NavigationMenu') {
      NavigationMenu();
    }
  }

  build() {
    Navigation(this.navPathStack) {
      Column() {
        Button('跳转').onClick(() => {
          this.navPathStack.pushPathByName('NavigationMenu', null);
        })
      }
    }
    .backButtonIcon(new SymbolGlyphModifier($r('sys.symbol.ohos_wifi')))
    .titleMode(NavigationTitleMode.Mini)
    .menus(this.menuItems)
    .toolbarConfiguration(this.toolItems)
    .title('一级页面')
    .navDestination(this.myRouter)
  }
}

@Component
export struct NavigationMenu {
  @Consume('navPathStack') navPathStack: NavPathStack;
  @State menuItems: Array<NavigationMenuItem> = [
    {
      value: 'menuItem1',
      icon: 'resources/base/media/ic_public_ok.svg', // 图标资源路径
      action: () => {
      }
    },
    {
      value: 'menuItem2',
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.ohos_folder_badge_plus')).fontColor([Color.Red, Color.Green])
        .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_COLOR),
      action: () => {
      }
    },
    {
      value: 'menuItem3',
      symbolIcon: new SymbolGlyphModifier($r('sys.symbol.repeat_1')),
      action: () => {
      }
    },
  ];

  build() {
    NavDestination() {
      Row() {
        Column() {
        }
        .width('100%')
      }
      .height('100%')
    }
    .hideTitleBar(false)
    .title('NavDestination title')
    .backgroundColor($r('sys.color.ohos_id_color_titlebar_sub_bg'))
    .backButtonIcon(new SymbolGlyphModifier($r('sys.symbol.ohos_star')).fontColor([Color.Blue]))
    .menus(this.menuItems)
  }
}
用 router+Tabs 实现(替代方案)

如果非要用 router 实现,需要手动管理 Tabs 和路由的联动,代码量会多很多:

import { router } from '@kit.ArkUI';


@Entry
@Component
struct TabsExample {
  @State fontColor: string = '#182431';
  @State selectedFontColor: string = '#007DFF';
  @State currentIndex: number = 0;
  @State selectedIndex: number = 0;
  private controller: TabsController = new TabsController();

  @Builder
  tabBuilder(index: number, name: string) {
    Column() {
      Text(name)
        .fontColor(this.selectedIndex === index ? this.selectedFontColor : this.fontColor)
        .fontSize(16)
        .fontWeight(this.selectedIndex === index ? 500 : 400)
        .lineHeight(22)
        .margin({ top: 17, bottom: 7 })
      Divider()
        .strokeWidth(2)
        .color('#007DFF')
        .opacity(this.selectedIndex === index ? 1 : 0)
    }.width('100%')
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
        TabContent() {
          Column().width('100%').height('100%').backgroundColor('#00CB87')
            .onClick(() => {
              // todo: 要跳转的地址和传递的参数
              // name: '',
              // params: {}
              // router.pushUrl()
            })
        }.tabBar(this.tabBuilder(0, 'green'))

        TabContent() {
          Column().width('100%').height('100%').backgroundColor('#007DFF')
            .onClick(() => {
              // todo: 要跳转的地址和传递的参数
              // name: '',
              // params: {}
              // router.pushUrl()
            })
        }.tabBar(this.tabBuilder(1, 'blue'))

        TabContent() {
          Column().width('100%').height('100%').backgroundColor('#FFBF00')
            .onClick(() => {
              // todo: 要跳转的地址和传递的参数
              // name: '',
              // params: {}
              // router.pushUrl()
            })
        }.tabBar(this.tabBuilder(2, 'yellow'))

        TabContent() {
          Column().width('100%').height('100%').backgroundColor('#E67C92')
            .onClick(() => {
              // todo: 要跳转的地址和传递的参数
              // name: '',
              // params: {}
              // router.pushUrl()
            })
        }.tabBar(this.tabBuilder(3, 'pink'))
      }
      .vertical(false)
      .barMode(BarMode.Fixed)
      .barWidth(360)
      .barHeight(56)
      .animationDuration(400)
      .onChange((index: number) => {
        // currentIndex控制TabContent显示页签
        this.currentIndex = index;
        this.selectedIndex = index;
      })
      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
        if (index === targetIndex) {
          return;
        }
        // selectedIndex控制自定义TabBar内Image和Text颜色切换
        this.selectedIndex = targetIndex;
      })
      .width(360)
      .height(296)
      .margin({ top: 52 })
      .backgroundColor('#F1F3F5')
    }.width('100%')
  }
}

从代码量和维护成本就能看出,navigation 在实现标准化导航时的优势非常明显。

四、实战选型建议:结合项目需求做选择

根据我在多个鸿蒙项目中的实践经验,总结出以下选型原则:

优先选 navigation 的情况

  1. 项目周期紧张,需要快速搭建标准化导航界面

  2. 应用有明确的多标签页或侧边导航结构

  3. 要求所有页面导航风格统一,无需高度自定义

  4. 开发团队中新手较多,需要降低 UI 开发难度

优先选 router 的情况

  1. 只需简单的页面跳转,无复杂导航需求

  2. 导航 UI 需要高度定制,比如带渐变背景的标题栏、集成搜索框的导航栏

  3. 跨模块跳转场景多,需要灵活控制路由栈

  4. 开发纯逻辑层的页面跳转(如后台服务跳转页面)

混合使用的最佳实践

在实际项目中,混合使用二者往往能达到最佳效果。比如:

  1. 用 navigation 搭建 APP 的整体框架(底部标签栏 + 顶部导航栏)

  2. 在每个标签页内部,用 router 实现页面间的跳转

  3. 跨标签页的跳转用 router 处理,保持导航框架的稳定性

比如电商 APP 中,底部 “首页 - 分类 - 购物车 - 我的” 用 navigation 实现,首页内部从商品列表到详情页的跳转用 router 实现,既保证了整体导航的标准化,又兼顾了页面跳转的灵活性。

五、总结

router 和 navigation 不是对立关系,而是互补的工具。router 专注于 “跳转逻辑”,是鸿蒙开发中轻量跳转的首选;navigation 专注于 “完整导航体验”,能大幅降低标准化导航的开发成本。

在实际开发中,关键是根据具体需求判断:如果需要处理的是 “页面间的跳转关系”,用 router;如果需要搭建 “带 UI 的导航结构”,用 navigation。掌握二者的核心差异,合理搭配使用,才能高效开发出体验优秀的鸿蒙应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值