Administrator
发布于 2026-08-30 / 95 阅读
1
0

鸿蒙页面路由与导航:Router 与 Navigation 的深度对比与选型指南

系列文章《鸿蒙开发从入门到专家》第 4 篇(中级篇)

前言

前三篇我们把界面搭出来了,但一个真实的应用不可能只有一个页面。页面之间怎么跳转?参数怎么传递?返回时数据怎么回传?这些问题的答案取决于你选哪种导航方案。

鸿蒙目前提供两套路由机制:

  • Router:从早期版本继承的页面路由 API,类似 Android 的 Intent

  • Navigation:HarmonyOS NEXT 主推的新一代导航组件,基于组件化的栈管理

很多开发者的困惑在于:网上搜到的教程混用两套 API,有的用 router.pushUrl(),有的用 Navigation() 组件,到底该学哪个?本文直接给结论:新项目一律用 Navigation,然后告诉你为什么。

一、Router:简单但有限的传统方案

1.1 基本用法

import { router } from '@kit.ArkUI'

// 跳转到新页面
router.pushUrl({
  url: 'pages/DetailPage',
  params: { id: '123', title: '新闻标题' }
})

// 返回上一页
router.back()

// 返回并携带结果
router.back({
  url: 'pages/Index',
  params: { updated: true }
})

1.2 接收参数

import { router } from '@kit.ArkUI'

@Entry
@Component
struct DetailPage {
  @State id: string = ''
  @State title: string = ''

  aboutToAppear() {
    let params = router.getParams() as Record<string, string>
    this.id = params['id'] || ''
    this.title = params['title'] || ''
  }

  build() {
    Column() {
      Text(this.title).fontSize(20)
      Text(`ID: ${this.id}`).fontSize(14).fontColor('#999')
    }
  }
}

1.3 Router 的局限性

问题

说明

无转场动画控制

页面切换默认没有动画,或动画生硬

栈管理薄弱

只能简单 push/pop,不支持复杂的栈操作

无生命周期感知

无法监听页面显示/隐藏事件

模块化支持差

跨 HSP/HAR 模块跳转配置繁琐

状态栏适配弱

页面内容无法安全扩展到状态栏下方

Router 适合快速原型或只有一个入口的简单页面,但企业级应用肯定会踩到上面的坑。

二、Navigation:鸿蒙主推的现代导航方案

Navigation 是 ArkUI 提供的声明式导航组件,它将页面栈管理内嵌到组件树中,配合 NavPathStack 实现完整的导航能力。

2.1 基础结构

import { NavPathStack } from '@kit.ArkUI'

@Entry
@Component
struct RootPage {
  @Provide('navStack') navStack: NavPathStack = new NavPathStack()

  @Builder
  PageMap(name: string) {
    if (name === 'HomePage') {
      HomePage()
    } else if (name === 'DetailPage') {
      DetailPage()
    } else if (name === 'SettingsPage') {
      SettingsPage()
    }
  }

  build() {
    Navigation(this.navStack) {
      // 默认显示的首页
      HomePage()
    }
    .hideTitleBar(true)           // 隐藏默认标题栏(我们自定义)
    .navDestination(this.PageMap) // 页面映射表
    .width('100%')
    .height('100%')
  }
}

2.2 页面跳转与参数传递

// HomePage.ets
@Component
struct HomePage {
  @Consume('navStack') navStack: NavPathStack

  build() {
    Column() {
      List() {
        ListItem() {
          Text('点击跳转到详情页')
            .onClick(() => {
              this.navStack.pushPathByName('DetailPage', {
                id: 'news-001',
                title: 'HarmonyOS NEXT 5.0 发布'
              })
            })
        }
      }
    }
  }
}

// DetailPage.ets
@Component
struct DetailPage {
  @Consume('navStack') navStack: NavPathStack

  // 通过 onReady 获取导航参数
  onReady?: (context: NavDestinationContext) => void

  build() {
    NavDestination() {
      Column() {
        Text('详情页内容')
        Button('返回')
          .onClick(() => {
            this.navStack.pop()
          })
      }
    }
    .onReady((context: NavDestinationContext) => {
      let params = context.pathInfo.param as Record<string, string>
      console.info(`收到参数: ${JSON.stringify(params)}`)
    })
    .hideTitleBar(true)
  }
}

2.3 页面生命周期:Navigation 的一大优势

API 26 为 Navigation 新增了 onWillShow 和 onWillHide 生命周期,这对数据刷新场景极其有用:

NavDestination() {
  Column() {
    Text(this.pageTitle)
  }
}
.onReady((context) => {
  // 页面首次创建时触发
  this.pageTitle = context.pathInfo.param?.title || ''
})
.onWillShow(() => {
  // 页面即将显示时触发(包括从其他页面返回时)
  console.info('DetailPage will show')
  this.refreshData()
})
.onWillHide(() => {
  // 页面即将隐藏时触发
  console.info('DetailPage will hide')
})
.onDisappear(() => {
  // 页面销毁时触发
  console.info('DetailPage disappeared')
})

对比 Router:Router 只有页面的 aboutToAppear 和 aboutToDisappear,无法区分"首次创建"和"从其他页面返回"。Navigation 的 onWillShow 在返回时也会触发,这是实现"返回刷新"的关键。

三、Navigation 高阶能力

3.1 转场动画自定义

Navigation 支持页面转场动画的自定义,API 26 还新增了动画打断和接续能力:

Navigation(this.navStack) {
  HomePage()
}
.navDestination(this.PageMap)
// 自定义转场动画
.transition(TransitionEffect.OPACITY.animation({ duration: 300 }))

更精细的控制可以在每个 NavDestination 上单独设置:

NavDestination() {
  // 页面内容
}
// 入场动画:从右侧滑入
.transition(TransitionEffect.translate({ x: '100%' }).animation({ duration: 250 }))
// 出场动画:向右侧滑出
.exitTransition(TransitionEffect.translate({ x: '100%' }).animation({ duration: 250 }))

3.2 页面内容扩展到状态栏

现代 App 普遍要求页面内容延伸到状态栏下方(沉浸式效果),Navigation 原生支持:

NavDestination() {
  Column() {
    // 顶部安全区域占位
    Row().height(this.statusBarHeight)
    
    // 真正的内容区域
    Text('标题').fontSize(20)
    // ...
  }
}
.hideTitleBar(true)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])  // 扩展到状态栏

3.3 单例模式跳转

有些页面(如登录页、设置页)在栈中应该只有一个实例,重复跳转时不创建新页面:

// 单例跳转:如果栈中已存在该页面,直接回到它
this.navStack.pushPathByName('SettingsPage', {}, { launchMode: LaunchMode.SINGLETON })

3.4 跨模块路由(HSP/HAR 动态路由)

API 26 新增了一个重要能力:业务模块(HSP/HAR)支持配置 router_map.json 路由表,实现模块间动态路由解耦。

// 模块内的 router_map.json
{
  "routerMap": [
    {
      "name": "FeatureDetailPage",
      "pageSourceFile": "src/main/ets/pages/FeatureDetailPage.ets",
      "buildFunction": "FeatureDetailPageBuilder"
    }
  ]
}

主模块在 module.json5 中引入该路由表后,就可以通过名称跳转到子模块的页面,无需硬编码路径。这是模块化架构的关键基础设施。

四、Router vs Navigation 选型决策表

维度

Router

Navigation

学习成本

低,API 简单

中,需要理解栈管理

转场动画

几乎不可控

完全可控,支持自定义

生命周期

只有基础钩子

完整的 willShow/willHide

栈操作

仅 push/pop

支持复杂的栈操作和单例模式

状态栏适配

需手动处理

原生支持 expandSafeArea

模块化支持

弱

强,支持 router_map 动态路由

华为官方态度

维护中,不新增特性

主推,持续增强

推荐场景

简单原型、单页应用

企业级应用、多页面应用

结论:除非你的应用只有一个页面,否则请直接上 Navigation。前期多花 30 分钟学习,后期能省掉大量路由相关的 bug 和重构工作。

五、实战:搭建带导航的完整应用骨架

// AppEntry.ets(应用入口)
import { NavPathStack } from '@kit.ArkUI'

@Entry
@Component
struct AppEntry {
  @Provide('navStack') navStack: NavPathStack = new NavPathStack()

  @Builder
  PageMap(name: string) {
    if (name === 'MainTabs') {
      MainTabsPage()
    } else if (name === 'SearchPage') {
      SearchPage()
    } else if (name === 'DetailPage') {
      DetailPage()
    }
  }

  build() {
    Navigation(this.navStack) {
      MainTabsPage()
    }
    .hideTitleBar(true)
    .navDestination(this.PageMap)
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

// MainTabsPage.ets(底部 Tab 容器)
@Component
struct MainTabsPage {
  @Consume('navStack') navStack: NavPathStack
  @State currentTab: number = 0

  build() {
    Tabs({ barPosition: BarPosition.End }) {
      TabContent() { HomeTabContent() }
        .tabBar(this.buildTabBar('首页', 0))
      
      TabContent() { CategoryTabContent() }
        .tabBar(this.buildTabBar('分类', 1))
      
      TabContent() { CartTabContent() }
        .tabBar(this.buildTabBar('购物车', 2))
      
      TabContent() { ProfileTabContent() }
        .tabBar(this.buildTabBar('我的', 3))
    }
    .onChange((index: number) => this.currentTab = index)
  }

  @Builder
  buildTabBar(title: string, index: number) {
    Column() {
      Text(title)
        .fontSize(12)
        .fontColor(this.currentTab === index ? '#317AF7' : '#999999')
    }
    .height(56)
    .justifyContent(FlexAlign.Center)
  }
}

// HomeTabContent.ets(首页内容)
@Component
struct HomeTabContent {
  @Consume('navStack') navStack: NavPathStack

  build() {
    Column() {
      // 顶部搜索栏(点击跳转到搜索页)
      Row() {
        Text('搜索商品')
          .fontColor('#999999')
          .layoutWeight(1)
      }
      .width('100%')
      .height(40)
      .padding({ left: 16, right: 16 })
      .backgroundColor('#F0F0F0')
      .borderRadius(20)
      .onClick(() => {
        this.navStack.pushPathByName('SearchPage', {})
      })

      // 商品列表(点击跳转到详情页)
      List() {
        LazyForEach(this.productList, (item: Product) => {
          ListItem() {
            // ... 商品卡片布局
          }
          .onClick(() => {
            this.navStack.pushPathByName('DetailPage', { productId: item.id })
          })
        })
      }
    }
  }
}

这个骨架的特点:

  1. Navigation 作为根容器,管理整个应用的页面栈

  2. Tabs 嵌套在 Navigation 内部,管理底部导航

  3. 任何深层页面都可以通过 this.navStack.pushPathByName() 跳转,无需层层传递

  4. 返回时 onWillShow 自动触发,适合实现"返回刷新列表"

小结与下篇预告

本文的核心结论很简单:新项目用 Navigation,别用 Router。

Navigation 的优势不是某一项功能,而是完整的导航体验:可控的转场动画、完整的生命周期、安全区域适配、模块化路由支持。这些是 Router 无法弥补的架构级差距。

下一篇(第 5 篇)我们进入数据层:鸿蒙的三种数据持久化方案——Preferences(轻量键值)、RelationalStore(关系型数据库)和分布式数据管理。你会学到什么时候该用哪种,以及它们各自的性能边界。

参考阅读


评论