ArkUI Navigation 路由跳转教程

孜航 33 阅读

适合零基础新手,跟着做就能学会页面跳转。


一、Navigation 是什么?

Navigation 是鸿蒙 ArkUI 提供的页面导航组件,专门用来做页面之间的跳转。

你可以把它想象成一摞书

 ┌─────────────────────┐
 │   详情页 (第2层)       │  ← 点按钮后叠上去的
 ├─────────────────────┤
 │   首页 (第1层)         │  ← 永远在最底下
 └─────────────────────┘
      Navigation 书架
  • 跳转到新页面 = 往上叠一本书(push)

  • 返回上一页 = 把最上面的书拿走(pop)


二、需要改哪些文件?

一共需要动 5 个文件,每个文件只干一件事:

 entry/src/main/
 ├── module.json5                          ← ① 告诉框架路由表在哪
 ├── resources/base/profile/
 │   └── router_map.json                   ← ② 路由表:名字 → 文件 → 函数
 └── ets/pages/
     ├── Index.ets                         ← ③ 老板:创建导航,提供 pathStack
     ├── HomePage.ets                      ← ④ 首页:默认内容,负责"跳出去"
     └── DetailPage.ets                    ← ⑤ 子页:子页面内容,负责"跳回来"

三、一步一步来

第 1 步:配置 module.json5(告诉框架路由表在哪)

文件位置: entry/src/main/module.json5

module 对象里加一行 routerMap

 {
   "module": {
     "name": "entry",
     "type": "entry",
     // ... 其他内容保持不动 ...
 ​
     // ★ 在最后加上这一行
     "routerMap": "$profile:router_map"
   }
 }

这一行的意思: 告诉框架"去 resources/base/profile/ 目录下找 router_map.json 这个路由表"。


第 2 步:创建路由表 router_map.json

文件位置: entry/src/main/resources/base/profile/router_map.json

如果文件已存在,把内容改成这样:

 {
   "routerMap": [
     {
       "name": "DetailPage",
       "pageSourceFile": "src/main/ets/pages/DetailPage.ets",
       "buildFunction": "DetailPageBuilder"
     }
   ]
 }

每一行的意思:

字段

意思

name

"DetailPage"

跳转时用的名字,和代码里 pushPath({ name: 'DetailPage' }) 对应

pageSourceFile

"src/main/ets/pages/DetailPage.ets"

这个页面的代码在哪个文件

buildFunction

"DetailPageBuilder"

用这个文件里的哪个函数来创建页面

以后每加一个新页面,就在 routerMap 数组里加一条。


第 3 步:写 Index.ets(老板:创建导航,提供 pathStack)

文件位置: entry/src/main/ets/pages/Index.ets

 import {HomePage} from "./HomePage"
 ​
 @Entry
 @Component
 struct Index {
   // ★ @Provide:把 pathStack 提供给所有后代组件
   // 子页面用 @Consume 就能拿到同一个实例
   @Provide('pathStack') pathStack: NavPathStack = new NavPathStack()
 ​
   build() {
     // 创建导航组件,绑定页面栈
     Navigation(this.pathStack) {
       // 默认显示首页
       HomePage()
     }
     .mode(NavigationMode.Stack)   // 单栏模式(手机用这个)
     .title('我的第一个App')         // 顶部标题
   }
 }

逐行解释:

代码

干什么

@Provide('pathStack')

pathStack 提供给所有后代组件,后代用 @Consume 就能拿到

new NavPathStack()

创建一个"页面栈",记录当前有哪些页面

Navigation(this.pathStack)

创建导航组件,绑定页面栈

HomePage()

导航的默认内容(首页)

.mode(NavigationMode.Stack)

手机用单栏模式(一次只显示一页)

.title(...)

顶部标题栏的文字


第 4 步:写 HomePage.ets(首页:负责"跳出去")

文件位置: entry/src/main/ets/pages/HomePage.ets

 @Component
 export struct HomePage {
   // ★ @Consume:从祖先组件(Index)拿到同一个 pathStack
   @Consume('pathStack') pathStack: NavPathStack
 ​
   build() {
     Column() {
       Text('🏠 这是首页')
         .fontSize(30)
         .fontWeight(FontWeight.Bold)
         .margin({ bottom: 40 })
 ​
       // 点击按钮 → 跳转到详情页
       Button('去详情页')
         .fontSize(20)
         .width(200)
         .height(50)
         .onClick(() => {
           // ★ 核心:告诉页面栈"下一页叫 DetailPage"
           this.pathStack.pushPath({ name: 'DetailPage' })
         })
     }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Center)
   }
 }

关键代码:

 this.pathStack.pushPath({ name: 'DetailPage' })

这一行做了三件事:

  1. DetailPage 这个名字写到页面栈里

  2. 框架拿着这个名字去 router_map.json 查找

  3. 找到对应的 DetailPageBuilder 函数,创建页面并显示


第 5 步:写 DetailPage.ets(子页:负责"跳回来")

文件位置: entry/src/main/ets/pages/DetailPage.ets

 // ★ 第一部分:@Builder 函数(框架调用它来创建页面)
 @Builder
 export function DetailPageBuilder() {
   // ★ 必须用 NavDestination 包裹,框架才能识别
   NavDestination() {
     DetailPage()
   }
   .title('详情页')    // 这一页的标题,标题栏自带返回按钮
 }
 ​
 // ★ 第二部分:页面组件(写具体的内容)
 @Component
 export struct DetailPage {
   // ★ @Consume:从祖先组件拿到同一个 pathStack
   @Consume('pathStack') pathStack: NavPathStack
 ​
   build() {
     Column() {
       Text('📄 这是详情页')
         .fontSize(30)
         .fontWeight(FontWeight.Bold)
         .margin({ bottom: 40 })
 ​
       // 点击按钮 → 返回上一页
       Button('← 返回首页')
         .fontSize(20)
         .width(200)
         .height(50)
         .onClick(() => {
           // ★ 核心:把最上面的页面从栈里拿掉
           this.pathStack.pop()
         })
     }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Center)
   }
 }

这个文件有两个部分,缺一不可:

部分

代码

作用

@Builder 函数

DetailPageBuilder()

框架调用它来创建页面,名字必须和 router_map.json 里的 buildFunction 一样

NavDestination()

包裹在 @Builder

必须有,框架靠它识别这是导航目标页,标题栏自带返回按钮

@Component

DetailPage

实际的页面内容(文字、按钮等)


四、跳转流程图解

跳转到新页面(push)

 用户点击 "去详情页"
         │
         ▼
 HomePage 执行: pathStack.pushPath({ name: 'DetailPage' })
         │
         ▼
 框架拿着 "DetailPage" 去 router_map.json 查找
         │
         ▼
 找到: pageSourceFile = "DetailPage.ets", buildFunction = "DetailPageBuilder"
         │
         ▼
 框架调用 DetailPageBuilder()
         │
         ▼
 DetailPageBuilder 返回 NavDestination() { DetailPage() }
         │
         ▼
 框架把 NavDestination 挂到 Navigation 上面显示
         │
         ▼
 用户看到详情页 ✅

返回上一页(pop)

有两种方式返回:

方式一: 点击 NavDestination 标题栏自带的返回按钮(框架自动处理,不用写代码)

方式二: 点击自定义按钮,手动调用 pathStack.pop()

 用户点击返回
         │
         ▼
 pathStack.pop()
         │
         ▼
 页面栈把 "DetailPage" 划掉
         │
         ▼
 框架把详情页从 Navigation 上拿走
         │
         ▼
 用户看到底下的首页 ✅

五、@Provide / @Consume 是什么?

这是 ArkUI 官方的组件间数据共享机制,专门解决"祖先组件的数据怎么传给后代组件"的问题。

 Index(祖先)
   │  @Provide('pathStack') pathStack     ← 提供数据
   │
   ├── HomePage(后代)
   │     @Consume('pathStack') pathStack  ← 消费数据,拿到同一个实例
   │
   └── NavDestination(后代)
         └── DetailPage(后代的后代)
               @Consume('pathStack') pathStack  ← 也能拿到,不管嵌套多深

关键点:

  • @Provide@Consume 里的字符串标识(比如 'pathStack')必须完全一致

  • @Consume 不管嵌套多少层都能拿到,只要祖先有 @Provide

  • 拿到的是同一个实例,不是复制的


六、怎么加新页面?

假设你要加一个设置页(SettingsPage),只需要改 3 个文件:

① router_map.json —— 加一条路由

 {
   "routerMap": [
     {
       "name": "DetailPage",
       "pageSourceFile": "src/main/ets/pages/DetailPage.ets",
       "buildFunction": "DetailPageBuilder"
     },
     {
       "name": "SettingsPage",
       "pageSourceFile": "src/main/ets/pages/SettingsPage.ets",
       "buildFunction": "SettingsPageBuilder"
     }
   ]
 }

② 新建 SettingsPage.ets —— 照着 DetailPage 的格式写

 @Builder
 export function SettingsPageBuilder() {
   NavDestination() {
     SettingsPage()
   }
   .title('设置')
 }
 ​
 @Component
 export struct SettingsPage {
   @Consume('pathStack') pathStack: NavPathStack
 ​
   build() {
     Column() {
       Text('⚙️ 这是设置页')
         .fontSize(30)
         .fontWeight(FontWeight.Bold)
         .margin({ bottom: 40 })
 ​
       Button('← 返回')
         .fontSize(20)
         .width(200)
         .height(50)
         .onClick(() => {
           this.pathStack.pop()
         })
     }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Center)
   }
 }

③ 任何页面里 —— 写一行跳转代码

 this.pathStack.pushPath({ name: 'SettingsPage' })

就这三步,不需要改 Index.ets,不需要改 module.json5。


七、子页面之间怎么跳?

比如从详情页跳到设置页,不用先返回首页再跳。直接在详情页里写:

 this.pathStack.pushPath({ name: 'SettingsPage' })

页面栈会变成:

 ┌──────────────┐
 │  设置页        │  ← 栈顶(当前显示)
 ├──────────────┤
 │  详情页        │
 ├──────────────┤
 │  首页          │  ← 栈底
 └──────────────┘

在设置页点返回,回到详情页。再点返回,回到首页。


八、pushPath 还能传参数

跳转时可以带数据过去:

 // 发送方:带上参数
 this.pathStack.pushPath({
   name: 'DetailPage',
   param: { title: '你好', id: 42 }
 })

接收方在 @Builder 函数里通过第二个参数拿到:

 @Builder
 export function DetailPageBuilder(param: object) {
   NavDestination() {
     DetailPage({ title: param.title })
   }
   .title('详情页')
 }

九、常见错误速查

症状

原因

解决

子页面白屏,只有返回按钮

@Builder 里没包 NavDestination()

加上 NavDestination() { ... }

编译报错 buildFunction does not exist

@Builder 函数名和 router_map.json 里的 buildFunction 不一致

改成完全一样的名字

编译报错 does not meet UI component syntax

@Builder 函数里写了非 UI 代码(比如 hilog.info

@Builder 里只能写 UI 组件,日志放到 aboutToAppear

点按钮没反应

pathStack 没拿到

检查 @Provide@Consume 的字符串标识是否一致

module.json5 报错

routerMap 那行的逗号或引号写错了

用 JSON5 格式检查,注意尾逗号


十、完整代码对照表

Index.ets

 import {HomePage} from "./HomePage"
 ​
 @Entry
 @Component
 struct Index {
   @Provide('pathStack') pathStack: NavPathStack = new NavPathStack()
 ​
   build() {
     Navigation(this.pathStack) {
       HomePage()
     }
     .mode(NavigationMode.Stack)
     .title('我的第一个App')
   }
 }

HomePage.ets

 @Component
 export struct HomePage {
   @Consume('pathStack') pathStack: NavPathStack
 ​
   build() {
     Column() {
       Text('🏠 这是首页')
         .fontSize(30)
         .fontWeight(FontWeight.Bold)
         .margin({ bottom: 40 })
 ​
       Button('去详情页')
         .fontSize(20)
         .width(200)
         .height(50)
         .onClick(() => {
           this.pathStack.pushPath({ name: 'DetailPage' })
         })
     }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Center)
   }
 }

DetailPage.ets

 @Builder
 export function DetailPageBuilder() {
   NavDestination() {
     DetailPage()
   }
   .title('详情页')
 }
 ​
 @Component
 export struct DetailPage {
   @Consume('pathStack') pathStack: NavPathStack
 ​
   build() {
     Column() {
       Text('📄 这是详情页')
         .fontSize(30)
         .fontWeight(FontWeight.Bold)
         .margin({ bottom: 40 })
 ​
       Button('← 返回首页')
         .fontSize(20)
         .width(200)
         .height(50)
         .onClick(() => {
           this.pathStack.pop()
         })
     }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Center)
   }
 }

router_map.json

 {
   "routerMap": [
     {
       "name": "DetailPage",
       "pageSourceFile": "src/main/ets/pages/DetailPage.ets",
       "buildFunction": "DetailPageBuilder"
     }
   ]
 }

module.json5(只需加一行)

 {
   "module": {
     // ... 其他内容保持不动 ...
     "routerMap": "$profile:router_map"
   }
 }

十一、总结:记住这张表

你想做的事

用什么

在哪写

创建导航

Navigation(pathStack)

Index.ets 的 build()

提供 pathStack

@Provide('pathStack')

Index.ets

获取 pathStack

@Consume('pathStack')

子页面组件里

配置路由表

router_map.json + module.json5

resources/profile/ 和 module.json5

写子页面

@Builder + NavDestination() + @Component

新建 .ets 文件

跳转到新页面

pathStack.pushPath({ name: 'xxx' })

任意页面的按钮事件里

返回上一页

pathStack.pop() 或点标题栏返回按钮

子页面

传参数

pushPath({ name: 'xxx', param: {...} })

pushPath 的 param 字段