HarmonyOS应用开发-Navigation 路由表详解

路由表是 Navigation 框架中将"页面名称"映射到"页面组件"的核心机制 。你的所有页面都通过路由表来注册,Navigation 再根据 pushPathByName('name') 中的 name 去路由表中查找并构建对应的组件。1


一、路由表是什么?

arduino 复制代码
pushPathByName('Detail')
        │
        ▼
   ┌─────────────────────┐
   │     路由表(映射)     │
   │                     │
   │  'Home'   →  HomePage   │
   │  'Detail' →  DetailPage │
   │  'Login'  →  LoginPage  │
   └─────────────────────┘
        │
        ▼
   构建 DetailPage 组件 → 显示

路由表本质上回答一个问题:"当我用某个名字跳转时,到底要显示哪个页面?"


二、三种路由表方案

方案 复杂度 适用场景 懒加载 跨包解耦
@Builder 内联映射 小型应用,页面少的模块内导航
系统路由表 ⭐⭐ 中大型应用,多模块(HAR/HSP)
自定义路由表 ⭐⭐⭐ 需要定制路由逻辑的复杂场景

三、方案一:@Builder 内联映射(最简)

3.1 核心代码

直接在 Navigation 所在的 @Entry 中写一个 @Builder 函数,用 if-else 做映射。

typescript 复制代码
@Entry
@Component
struct MainEntry {
  pageStack: NavPathStack = new NavPathStack();

  // ⭐ 路由表:一个 @Builder 函数搞定
  @Builder
  pageMap(name: string, param: Object | undefined) {
    if (name === 'Home') {
      HomePage()
    } else if (name === 'Detail') {
      DetailPage({ item: param as ItemData })
    } else if (name === 'Search') {
      SearchPage({ keyword: param as string })
    }
    // 这里可以继续扩展更多页面
  }

  build() {
    Navigation(this.pageStack) {
      Column() {
        Button('去详情').onClick(() => {
          this.pageStack.pushPathByName('Detail', { id: 1 });
        })
      }
    }
    .navDestination(this.pageMap)   // 注册路由表
  }
}

3.2 优点与局限

优点:

  • 零配置,代码即路由
  • 参数类型可直接在调用处推断
  • 适合学习和小型项目

局限:

  • 所有页面必须在同一个文件或多个文件中静态 import,模块间耦合
  • 首屏加载时所有页面组件都会被打包,无法按需加载
  • 页面增多后 if-else 链变长,难以维护
  • 不支持跨 HAR/HSP 模块的路由23

四、方案二:系统路由表(推荐)

系统路由表是官方推荐的中大型项目方案,通过 JSON 配置文件 声明所有路由,Navigation 在运行时自动完成模块加载和页面构建。3

4.1 架构概览

arduino 复制代码
应用启动
    │
    ├── module.json5 声明路由表路径
    │     "routerMap": "$profile:route_map"
    │
    └── route_map.json 定义路由映射
           { name: "Detail", buildFunction: "DetailBuilder", ... }
                │
                ▼
          Navigation 按需动态 import → 构建 Builder → 显示页面

4.2 第一步:创建路由配置文件

entry/src/main/resources/base/profile/ 目录下创建 route_map.json

json 复制代码
{
  "routerMap": [
    {
      "name": "Home",
      "pageSourceFile": "src/main/ets/pages/HomePage.ets",
      "buildFunction": "HomePageBuilder",
      "data": {
        "description": "首页"
      }
    },
    {
      "name": "Detail",
      "pageSourceFile": "src/main/ets/pages/DetailPage.ets",
      "buildFunction": "DetailPageBuilder",
      "data": {
        "description": "详情页",
        "needAuth": false
      }
    },
    {
      "name": "Login",
      "pageSourceFile": "src/main/ets/pages/LoginPage.ets",
      "buildFunction": "LoginPageBuilder",
      "data": {
        "description": "登录页"
      }
    }
  ]
}

字段说明:

字段 类型 必填 说明
name string 页面唯一标识,pushPathByName 的参数
pageSourceFile string 页面文件路径(相对 src 目录)
buildFunction string 该页面导出的 @Builder 函数名
data object 自定义数据,可通过 getConfigInRouteMap 读取

4.3 第二步:在 module.json5 中注册

json5 复制代码
// entry/src/main/module.json5
{
  "module": {
    "name": "entry",
    "type": "entry",
    // ⭐ 关键配置
    "routerMap": "$profile:route_map",
    // ... 其他配置
  }
}

4.4 第三步:编写页面及其 Builder 函数

每个页面文件必须导出一个 @Builder 函数作为"页面入口"。

typescript 复制代码
// pages/DetailPage.ets

// ⭐ 必须导出的 Builder 函数,函数名与 route_map.json 中一致
@Builder
export function DetailPageBuilder() {
  DetailPage()
}

@Component
export struct DetailPage {
  // 从 NavDestinationContext 中获取导航栈和参数
  private pageStack: NavPathStack | null = null;
  private param: Object | undefined = undefined;

  build() {
    NavDestination() {
      Column() {
        Text('详情页')
        Button('返回')
          .onClick(() => {
            this.pageStack?.pop();
          })
      }
    }
    .title('详情')
    .onReady((ctx: NavDestinationContext) => {
      // ⭐ 在 onReady 中获取上下文
      this.pageStack = ctx.pathStack;
      this.param = ctx.pathInfo?.param;

      // 也可以通过 ctx 读取路由配置中的 data
      console.log('路由描述: ' + ctx.getConfigInRouteMap()?.data?.['description']);
    })
  }
}

关键设计:

scss 复制代码
系统路由表的页面加载流程:

pushPathByName('Detail')
        │
        ▼
① 查找 route_map.json → { name: "Detail", pageSourceFile: "...", buildFunction: "DetailPageBuilder" }
        │
        ▼
② 动态 import → 加载 DetailPage.ets
        │
        ▼
③ 调用 export 的 DetailPageBuilder() → 构建页面组件
        │
        ▼
④ NavDestination.onReady() → 获取上下文,完成初始化

4.5 入口页无需 .navDestination()

使用系统路由表时,不要在 Navigation 上调用 .navDestination(),否则会冲突:4

typescript 复制代码
@Entry
@Component
struct MainEntry {
  pageStack: NavPathStack = new NavPathStack();

  build() {
    Navigation(this.pageStack) {
      Column() {
        Button('去详情')
          .onClick(() => {
            // 直接用 pushPathByName,系统自动查路由表
            this.pageStack.pushPathByName('Detail', { id: 1 });
          })
      }
    }
    // ❌ 不要写 .navDestination(this.pageMap)
    // 系统路由表已经接管了映射逻辑
    .mode(NavigationMode.Stack)
    .title('应用')
  }
}

4.6 读取路由配置中的自定义数据

typescript 复制代码
NavDestination() {
  // ...
}
.onReady((ctx: NavDestinationContext) => {
  // 获取路由表中配置的 data 字段
  const config = ctx.getConfigInRouteMap();
  const description = config?.data?.['description'];  // "详情页"
  const needAuth = config?.data?.['needAuth'];         // false

  // 可以基于配置做业务逻辑
  if (needAuth && !isLogin) {
    ctx.pathStack.pushPathByName('Login', null);
  }
})

4.7 多模块(HAR/HSP)路由表

每个 HAR/HSP 模块都可以拥有自己的 route_map.json,实现路由的模块级自治:

css 复制代码
entry/
  └── src/main/resources/base/profile/route_map.json    ← 模块入口路由

feature_detail/
  └── src/main/resources/base/profile/route_map.json    ← 详情模块路由

feature_user/
  └── src/main/resources/base/profile/route_map.json    ← 用户模块路由

每个模块的 route_map.json 配置本模块的页面,pushPathByName 时系统会自动查找所有模块的路由表。1

4.8 系统路由表的优点与局限

优点:

  • 按需加载(动态 import),首屏加载快
  • 跨 HAR/HSP 模块天然解耦
  • 配置集中管理,页面增减只改 JSON
  • 支持在 data 中附加路由元信息

局限:

  • 不支持预览器(需在模拟器或真机上测试)
  • 页面参数类型丢失(需手动类型断言)
  • 无法做复杂的运行时路由逻辑

五、方案三:自定义路由表(高级)

当系统路由表无法满足需求时(如需要运行时动态注册路由、路由守卫、更复杂的参数处理),可以自建路由管理模块。3

5.1 核心思路

scss 复制代码
                自定义路由管理器 (RouterManager)
               ┌──────────────────────────────────────┐
               │  routeMap: Record<string, BuilderFn> │
               │                                      │
注册页面 ───────→  register(name, builder)              │
跳转 ──────────→  push(name, param)                    │
               │     ↓                                │
               │  动态 import 模块 → 查 routeMap →     │
               │  调用 builder → pushPath              │
               └──────────────────────────────────────┘

5.2 完整实现

typescript 复制代码
// ==================== 路由管理器 ====================
// router/RouterManager.ets

import { BusinessError } from '@kit.BasicServicesKit';

// 类型定义
type PageBuilder = WrappedBuilder<[object]>;

interface RouteConfig {
  name: string;            // 路由名称
  moduleName: string;      // 所属 HAR/HSP 模块名
  pagePath: string;        // 页面在模块中的路径
  builderName: string;     // Builder 函数名
  meta?: Record<string, Object>;  // 路由元信息
}

// 全局路由表
const ROUTE_MAP: Record<string, RouteConfig> = {
  'Home': {
    name: 'Home',
    moduleName: 'entry',
    pagePath: 'pages/HomePage',
    builderName: 'HomePageBuilder',
  },
  'Detail': {
    name: 'Detail',
    moduleName: 'feature_detail',
    pagePath: 'pages/DetailPage',
    builderName: 'DetailPageBuilder',
    meta: { needAuth: false },
  },
  'Login': {
    name: 'Login',
    moduleName: 'feature_user',
    pagePath: 'pages/LoginPage',
    builderName: 'LoginPageBuilder',
  },
};

// 已加载的 Builder 缓存
const builderCache: Map<string, PageBuilder> = new Map();

class RouterManager {
  private pageStack: NavPathStack | null = null;

  // 初始化:注入 NavPathStack
  init(stack: NavPathStack) {
    this.pageStack = stack;
  }

  // 获取已注册的所有路由名(用于路由拦截白名单等)
  getAllRouteNames(): string[] {
    return Object.keys(ROUTE_MAP);
  }

  // 异步加载目标页面的 Builder
  private async loadBuilder(routeName: string): Promise<PageBuilder> {
    // 缓存命中
    const cached = builderCache.get(routeName);
    if (cached) return cached;

    const config = ROUTE_MAP[routeName];
    if (!config) {
      throw new Error(`路由 "${routeName}" 未注册`);
    }

    try {
      // ⭐ 动态 import,按需加载
      const module = await import(
        `${config.moduleName}/src/main/ets/${config.pagePath}`
      );
      const builder = module[config.builderName] as PageBuilder;
      builderCache.set(routeName, builder);
      return builder;
    } catch (err) {
      const error = err as BusinessError;
      throw new Error(`加载路由 "${routeName}" 失败: ${error.message}`);
    }
  }

  // 跳转到指定页面
  async push(routeName: string, param?: Object, onPop?: Callback<PopInfo>) {
    if (!this.pageStack) {
      throw new Error('RouterManager 未初始化,请先调用 init()');
    }

    // 路由守卫:统一鉴权
    const config = ROUTE_MAP[routeName];
    if (config?.meta?.['needAuth'] && !AppState.isLogin) {
      this.pageStack.pushPathByName('Login', null);
      return;
    }

    // 正常跳转
    this.pageStack.pushPathByName(routeName, param, onPop);
  }

  // 返回
  pop(result?: Object) {
    this.pageStack?.pop(result);
  }

  // 返回到指定页面
  popTo(name: string) {
    this.pageStack?.popToName(name);
  }

  // 清空栈
  clear() {
    this.pageStack?.clear();
  }
}

// 单例导出
export const routerManager = new RouterManager();

5.3 入口中使用

typescript 复制代码
// EntryAbility.ets
import { routerManager } from '../router/RouterManager';

@Entry
@Component
struct MainEntry {
  pageStack: NavPathStack = new NavPathStack();

  @Builder
  pageMap(name: string, param: Object | undefined) {
    // 这里直接 push 已在 RouterManager 中处理过的路由
    // 或者通过 NavDestination 的 .navDestination 做中转
  }

  aboutToAppear() {
    // ⭐ 初始化路由管理器
    routerManager.init(this.pageStack);
  }

  build() {
    Navigation(this.pageStack) {
      Column()
    }
    .navDestination(this.pageMap)
  }
}

⚠️ 自定义路由表的完整跨包加载涉及 import() 动态导入和 WrappedBuilder 封装,实现较为复杂。团队规模较小或页面不多时,直接使用系统路由表即可。3


六、三种方案对比总结

css 复制代码
场景                      推荐方案
─────────────────────────────────────────
学习 / Demo / 2-3 个页面   方案一 @Builder 内联
单模块中等项目              方案二 系统路由表
多模块(HAR/HSP)中大项目    方案二 系统路由表
需要复杂路由层逻辑          方案三 自定义路由表

决策流程图

css 复制代码
是否需要跨 HAR/HSP 模块跳转?
    ├── 否 → 页面 ≤ 5 个?
    │         ├── 是 → 方案一 @Builder 内联
    │         └── 否 → 方案二 系统路由表
    │
    └── 是 → 需要运行时动态注册/卸载路由?
              ├── 是 → 方案三 自定义路由表
              └── 否 → 方案二 系统路由表(多模块各有 route_map.json)

七、常见问题

Q1:navDestination 和系统路由表能同时使用吗?

不能。 如果同时使用,navDestination 的优先级更高,系统路由表会失效。使用系统路由表时,删除 .navDestination() 即可。

Q2:系统路由表配置了但跳转失败?

依次检查:

  1. route_map.json 语法是否正确(JSON 格式)
  2. buildFunction 名称是否与导出的 @Builder 函数名完全一致
  3. pageSourceFile 路径是否正确(相对于 src 目录)
  4. 是否在模拟器或真机上测试(预览器不支持系统路由表)

Q3:路由表中 name 的值有什么限制?

无硬性限制,支持中文。但建议使用语义化英文名,且保证全局唯一。

Q4:如何获取当前正在显示的是哪个页面?

typescript 复制代码
// 监听页面切换
import { uiObserver } from '@kit.ArkUI';

uiObserver.on('navDestinationSwitch', (info: NavDestinationSwitchInfo) => {
  console.log(`当前页面: ${info.to}`);
});

路由表的核心价值在于 "名称解耦"------你的跳转代码只需要知道目的地的名字,而不需要知道它存在于哪个文件、哪个模块、如何构建。这正是 Navigation 框架相比 Router 在架构层面的质变。

相关推荐
云端漫步19872 小时前
HarmonyOS NEXT AI 智能生活助手:AI 待办事项生成
人工智能·华为·生活·harmonyos
烛衔溟2 小时前
HarmonyOS 网络连接 —— HTTP 请求、Axios 与 Socket 通信
http·华为·harmonyos
懿路向前3 小时前
【HarmonyOS学习笔记】2026-08-04 | 端插件新装饰器与CreateRecord全链路验证
笔记·学习·harmonyos
云端漫步19873 小时前
HarmonyOS NEXT AI 智能生活助手:AI 翻译助手
人工智能·华为·生活·harmonyos
世人万千丶11 小时前
鸿蒙日志体系高级应用:HiLog分级输出/隐私脱敏/远程日志采集/线上问题精准溯源方案
学习·harmonyos·鸿蒙
程序员黑豆17 小时前
鸿蒙应用开发:AttributeModifier 使用教程
前端·harmonyos
HarmonyOS_SDK18 小时前
基于人体骨骼点识别与跟踪,实现低时延体感游戏
harmonyos
世人万千丶19 小时前
鸿蒙Crash高级捕获与异常监控:全局异常兜底/崩溃栈解析/符号表还原/智能聚类/闭环修复
学习·机器学习·华为·数据挖掘·harmonyos·鸿蒙·聚类
云端漫步198720 小时前
HarmonyOS NEXT AI 智能生活助手:创建企业级 AI 工程与目录结构
人工智能·生活·harmonyos