本篇学习 Navigation 路由系统,构建完整的导航架构

页面路由与导航架构 教程结构图

图:页面路由与导航架构 的关键流程与实现要点。

学习目标

完成本篇后,你将能够:

  • ✅ 理解 Navigation 路由系统
  • ✅ 使用 NavPathStack 管理路由
  • ✅ 实现页面间参数传递
  • ✅ 设计完整导航架构

预计学习时间

约 120 分钟


实战一:理解路由系统

第一步:传统 router vs Navigation

特性 router Navigation
路由管理 全局单例 组件级路由栈
参数传递 params 对象 类型安全
嵌套路由 不支持 支持
动画控制 有限 灵活
推荐程度 了解即可 推荐使用

第二步:Navigation 核心概念

概念 说明
Navigation 路由容器组件
NavPathStack 路由栈,管理页面
NavDestination 目标页面包装器
navDestination 路由映射函数

第三步:路由架构示意

┌─────────────────────────────────────┐
│  MainPage (@Entry)                   │
│  ┌─────────────────────────────────┐│
│  │  Navigation(navStack)           ││
│  │    └─ HomePage (默认)           ││
│  │  .navDestination(router)        ││
│  └─────────────────────────────────┘│
└─────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────┐
│  子页面 (NavDestination)             │
│  - DictionaryPage                    │
│  - PositionDetailPage                │
│  - ExamPage                          │
└─────────────────────────────────────┘

实战二:创建 Navigation 容器

第一步:定义路由参数接口

// common/Types.ets
export interface NavRouteParams {
  id?: number;
  name?: string;
  dynasty?: string;
  data?: object;
}

第二步:创建主页面

// pages/MainPage.ets
import { HomePage } from './HomePage';
import { DictionaryPage } from './DictionaryPage';
import { PositionDetailPage } from './PositionDetailPage';

@Entry
@Component
struct MainPage {
  @Provide('mainNavPathStack') navStack: NavPathStack = new NavPathStack();

  @Builder
  mainRouter(name: string, param?: NavRouteParams) {
    if (name === 'HomePage') {
      HomePage()
    } else if (name === 'DictionaryPage') {
      DictionaryPage()
    } else if (name === 'PositionDetailPage') {
      PositionDetailPage()
    }
  }

  build() {
    Navigation(this.navStack) {
      // 默认显示首页
      HomePage()
    }
    .navDestination(this.mainRouter)
    .hideTitleBar(true)
    .mode(NavigationMode.Stack)
  }
}

第三步:理解关键配置

配置 说明
@Provide 向子组件提供路由栈
navDestination 绑定路由映射函数
hideTitleBar 隐藏默认标题栏
NavigationMode.Stack 栈式导航模式

实战三:创建子页面

第一步:子页面基本结构

// pages/DictionaryPage.ets
@Component
export struct DictionaryPage {
  @Consume('mainNavPathStack') navStack: NavPathStack;

  build() {
    NavDestination() {
      Column() {
        // 顶部导航栏
        Row() {
          Image($r('app.media.ic_back'))
            .width(24)
            .height(24)
            .onClick(() => {
              this.navStack.pop();
            })

          Text('职官词典')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .margin({ left: 16 })

          Blank()
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White)

        // 页面内容
        this.ContentView()
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f8f6f5')
    }
    .hideTitleBar(true)
  }

  @Builder
  ContentView() {
    // 页面具体内容
  }
}

// 导出 Builder 函数
@Builder
export function DictionaryPageBuilder() {
  DictionaryPage()
}

第二步:接收路由参数

// pages/PositionDetailPage.ets
@Component
export struct PositionDetailPage {
  @Consume('mainNavPathStack') navStack: NavPathStack;
  @State positionName: string = '';
  @State dynasty: string = '';

  aboutToAppear() {
    // 获取路由参数
    const params = this.navStack.getParamByName('PositionDetailPage');
    if (params && params.length > 0) {
      const param = params[0] as NavRouteParams;
      this.positionName = param.name || '';
      this.dynasty = param.dynasty || '';
    }
  }

  build() {
    NavDestination() {
      Column() {
        // 顶部导航栏
        Row() {
          Image($r('app.media.ic_back'))
            .width(24)
            .height(24)
            .onClick(() => {
              this.navStack.pop();
            })

          Text(this.positionName)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#1e293b')
            .margin({ left: 16 })

          Blank()
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White)

        // 详情内容
        Column({ space: 16 }) {
          Text(`官职:${this.positionName}`)
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .fontColor('#c41e3a')

          Text(`朝代:${this.dynasty}`)
            .fontSize(16)
            .fontColor('#64748b')
        }
        .width('100%')
        .padding(16)
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f8f6f5')
    }
    .hideTitleBar(true)
  }
}

@Builder
export function PositionDetailPageBuilder() {
  PositionDetailPage()
}

实战四:路由操作

第一步:跳转页面

// 无参数跳转
this.navStack.pushPathByName('DictionaryPage', {});

// 带参数跳转
this.navStack.pushPathByName('PositionDetailPage', {
  name: '丞相',
  dynasty: '秦'
} as NavRouteParams);

第二步:返回上一页

// 返回上一页
this.navStack.pop();

// 返回到指定页面
this.navStack.popToName('HomePage');

// 返回到根页面
this.navStack.clear();

第三步:替换当前页

// 替换当前页面
this.navStack.replacePath({
  name: 'ResultPage',
  param: { score: 100 }
});

第四步:获取路由信息

// 获取栈深度
const size = this.navStack.size();

// 获取参数
const params = this.navStack.getParamByName('PageName');

// 获取所有页面名称
const names = this.navStack.getAllPathName();

实战五:完整导航架构

第一步:创建完整的 MainPage

// pages/MainPage.ets
import { NavRouteParams } from '../common/Types';

@Entry
@Component
struct MainPage {
  @Provide('mainNavPathStack') navStack: NavPathStack = new NavPathStack();

  @Builder
  mainRouter(name: string, param?: NavRouteParams) {
    if (name === 'HomePage') {
      HomePageBuilder()
    } else if (name === 'DictionaryPage') {
      DictionaryPageBuilder()
    } else if (name === 'PositionDetailPage') {
      PositionDetailPageBuilder()
    } else if (name === 'ExamPage') {
      ExamPageBuilder()
    } else if (name === 'MatcherPage') {
      MatcherPageBuilder()
    } else if (name === 'NewYearCustomPage') {
      NewYearCustomPageBuilder()
    }
  }

  build() {
    Navigation(this.navStack) {
      HomePageBuilder()
    }
    .navDestination(this.mainRouter)
    .hideTitleBar(true)
    .mode(NavigationMode.Stack)
  }
}

第二步:首页带底部导航

// pages/HomePage.ets
@Component
export struct HomePage {
  @Consume('mainNavPathStack') navStack: NavPathStack;
  @State currentTab: number = 0;

  build() {
    NavDestination() {
      Column() {
        // 内容区域
        Stack() {
          if (this.currentTab === 0) {
            this.HomeContent()
          } else if (this.currentTab === 1) {
            this.ExploreContent()
          } else {
            this.MineContent()
          }
        }
        .layoutWeight(1)

        // 底部导航栏
        Row() {
          this.TabItem('首页', $r('app.media.ic_home'), 0)
          this.TabItem('探索', $r('app.media.ic_explore'), 1)
          this.TabItem('我的', $r('app.media.ic_person'), 2)
        }
        .width('100%')
        .height(56)
        .backgroundColor(Color.White)
        .justifyContent(FlexAlign.SpaceAround)
      }
      .width('100%')
      .height('100%')
    }
    .hideTitleBar(true)
  }

  @Builder
  TabItem(title: string, icon: Resource, index: number) {
    Column({ space: 4 }) {
      Image(icon)
        .width(24)
        .height(24)
        .fillColor(this.currentTab === index ? '#c41e3a' : '#9ca3af')

      Text(title)
        .fontSize(12)
        .fontColor(this.currentTab === index ? '#c41e3a' : '#9ca3af')
    }
    .onClick(() => {
      this.currentTab = index;
    })
  }

  @Builder
  HomeContent() {
    Scroll() {
      Column({ space: 16 }) {
        // 功能入口
        this.FeatureCard('职官词典', '查阅八朝官职', () => {
          this.navStack.pushPathByName('DictionaryPage', {});
        })

        this.FeatureCard('古今匹配', '找到你的古代职业', () => {
          this.navStack.pushPathByName('MatcherPage', {});
        })

        this.FeatureCard('科举考试', '体验古代科举', () => {
          this.navStack.pushPathByName('ExamPage', {});
        })
      }
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8f6f5')
  }

  @Builder
  FeatureCard(title: string, desc: string, onClick: () => void) {
    Row() {
      Column({ space: 4 }) {
        Text(title)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#1e293b')

        Text(desc)
          .fontSize(13)
          .fontColor('#64748b')
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Image($r('app.media.ic_chevron_right'))
        .width(20)
        .height(20)
        .fillColor('#9ca3af')
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .onClick(onClick)
  }

  @Builder
  ExploreContent() {
    Column() {
      Text('探索')
        .fontSize(18)
        .fontColor('#1e293b')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#f8f6f5')
  }

  @Builder
  MineContent() {
    Column() {
      Text('我的')
        .fontSize(18)
        .fontColor('#1e293b')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#f8f6f5')
  }
}

@Builder
export function HomePageBuilder() {
  HomePage()
}

第三步:运行验证

hvigorw assembleHap --no-daemon

本课小结

核心知识点

知识点 说明
Navigation 路由容器组件
NavPathStack 路由栈管理
NavDestination 目标页面包装
@Provide/@Consume 路由栈传递
pushPathByName 跳转并传参
pop 返回上一页

Navigation 迁移检查清单

步骤 说明
1 创建 MainPage 作为容器
2 定义 NavRouteParams 接口
3 实现 mainRouter 映射
4 子页面添加 @Consume
5 子页面用 NavDestination 包裹
6 导出 Builder 函数
7 替换 router 调用

课后练习

练习1:实现页面返回确认

在返回时弹出确认对话框。

练习2:实现路由拦截

在跳转前检查登录状态。


下一课预告

第19课我们将学习 WebView 与混合开发,包括:

  • Web 组件基础
  • JavaScript 与 ArkTS 通信
  • ECharts 地图集成

项目开源地址

https://gitcode.com/daleishen/gujinzhijian

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐