页面路由导航:Router与Navigation组件的跳转传参(7)

前言:选择正确的导航方案

在鸿蒙 ArkTS 开发中,页面跳转与导航是应用骨架的核心。目前 ArkUI 提供了两套路由方案:

  1. Navigation 组件(推荐):基于组件化的路由容器,适用于绝大多数场景,特别是需要复杂交互、多端适配(一次开发,多端部署)的应用。
  2. Router 模块(不推荐):基于页面路径的跳转方式,功能较基础,页面栈有上限(32层),主要用于简单的页面跳转或兼容旧代码。

本文将重点讲解官方推荐的 Navigation 组件,并简要对比 Router 模块。


Navigation 是一个路由导航的根视图容器,它支持单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式,能够根据窗口大小自动切换布局,非常适合折叠屏和平板设备。

核心概念:

  • NavPathStack:导航路径栈,用于管理页面的入栈(Push)、出栈(Pop)和替换(Replace)。
  • NavDestination :子页面容器,必须嵌套在 Navigation 组件中使用。

1. 基础路由配置与跳转

要实现 Navigation 路由,首先需要配置路由表,并在 module.json5 中注册。

步骤一:配置路由表 (router_map.json)

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

python 复制代码
{
  "routerMap": [
    {
      "name": "pageOne",
      "pageSourceFile": "src/main/ets/pages/PageOne.ets",
      "buildFunction": "PageOneBuilder"
    },
    {
      "name": "pageTwo",
      "pageSourceFile": "src/main/ets/pages/PageTwo.ets",
      "buildFunction": "PageTwoBuilder"
    }
  ]
}

注:buildFunction 是页面组件对应的 @Builder 函数名称。

步骤二:在 module.json5 中注册

python 复制代码
"module": {
  "routerMap": "$profile:router_map"
}

步骤三:主页面与跳转实现

TypeScript 复制代码
// Index.ets (主页面)
import { router } from '@kit.ArkUI';

@Entry
@Component
struct NavigationPage {
  // 创建导航路径栈
  navStack: NavPathStack = new NavPathStack();

  aboutToAppear() {
    // 将栈对象存入 AppStorage,方便子页面获取
    AppStorage.setOrCreate<NavPathStack>('navStack', this.navStack);
  }

  build() {
    Navigation(this.navStack) {
      // 导航页内容(首页内容)
      Column({ space: 20 }) {
        Text('这是首页')
          .fontSize(30)

        Button('跳转到 PageOne')
          .onClick(() => {
            // 方式1:通过名称跳转,可携带参数
            this.navStack.pushPath({ name: 'pageOne', param: '我是首页传来的参数' });
          })

        Button('跳转到 PageTwo (带回调)')
          .onClick(() => {
            // 方式2:带返回回调的跳转
            this.navStack.pushPathByName('pageTwo', '参数2', (popInfo) => {
              console.info('PageTwo 返回了:' + JSON.stringify(popInfo.result));
            });
          })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .mode(NavigationMode.Stack) // 设置为单栏模式
    .title('主标题')
    .hideTitleBar(false)
  }
}

步骤四:子页面接收参数与返回

TypeScript 复制代码
// src/main/ets/pages/PageOne.ets

// 1. Builder 函数:作为路由入口
// name 是路由名称(通常用不到),param 是传入的参数
@Builder
export function PageOneBuilder(name: string, param: Object) {
  // 【修正点】:只传入 value,去掉不存在的 name 属性
  // 注意:如果 param 可能是 undefined,建议做一下类型转换或判空
  PageOne({ value: param as string });
}

@Component
export struct PageOne {
  navPathStack: NavPathStack = new NavPathStack();

  // 定义接收参数的属性
  @State value: string = '';

  // 【新增】:在组件初始化时处理参数
  aboutToAppear() {
    // 这里可以做一些额外的初始化逻辑
    console.info('PageOne 已加载,参数为: ' + this.value);
  }

  build() {
    NavDestination() {
      Column({ space: 20 }) {
        Text(`接收到的参数: ${this.value}`)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)

        Button('返回上一页')
          .onClick(() => {
            // 普通返回
            this.navPathStack.pop();
          })

        Button('返回并带回数据')
          .backgroundColor('#007DFF')
          .fontColor(Color.White)
          .onClick(() => {
            // 带结果返回,触发上一页的回调
            this.navPathStack.pop({ result: '我是PageOne带回的数据' });
          })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .title('PageOne')
    // onReady 主要用于获取 Context 和 PathStack,不建议在这里做业务数据赋值
    .onReady((ctx: NavDestinationContext) => {
      this.navPathStack = ctx.pathStack;
    })
  }
}
TypeScript 复制代码
// src/main/ets/pages/PageTwo.ets

@Builder
export function PageTwoBuilder(name: string, param: string) {
  PageTwo({ value: param });
}

@Component
export struct PageTwo {
  value: string = '';
  @State showValue: string = '';
  private navPathStack: NavPathStack = new NavPathStack();

  build() {
    NavDestination() {
      Column({ space: 20 }) {
        Text(`PageTwo 收到: ${this.showValue}`)
          .fontSize(24)

        Button('返回并触发回调')
          .backgroundColor('#E65555')
          .onClick(() => {
            // 这里触发的 result 会回到 Index.ets 的 pushPathByName 回调中
            this.navPathStack.pop({ result: 'PageTwo 任务完成' });
          })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .title('PageTwo')
    .onReady((ctx: NavDestinationContext) => {
      this.navPathStack = ctx.pathStack;
      this.showValue = ctx.pathInfo?.param as string || '无参数';
    })
  }
}

二、 参数传递进阶:对象与数组的序列化陷阱

在使用 NavigationRouter 进行页面跳转传参时,如果传递的是复杂对象或数组,经常会遇到数据丢失或无法刷新的问题。

问题原因:

路由传参在底层会经历"序列化 + 反序列化"的过程。对于被 @ObservedV2@Trace 装饰的类对象,序列化后属性名会被添加 __ob_ 前缀,导致反序列化后失去观察能力,甚至属性名错乱。

解决方案:

  1. 基础类型/简单对象:直接传递,接收时注意类型断言。
  2. 复杂对象/数组 :建议使用 JSON.stringify() 序列化后传递,接收方再用 JSON.parse() 解析。

代码示例:

TypeScript 复制代码
// Index.ets (主页面)
import { router } from '@kit.ArkUI';

@Entry
@Component
struct NavigationPage {
  // 创建导航路径栈
  navStack: NavPathStack = new NavPathStack();

  aboutToAppear() {
    // 将栈对象存入 AppStorage,方便子页面获取
    AppStorage.setOrCreate<NavPathStack>('navStack', this.navStack);
  }

  build() {
    Navigation(this.navStack) {
      // 导航页内容(首页内容)
      Column({ space: 20 }) {
        Text('这是首页')
          .fontSize(30)

        Button('跳转到 PageOne')
          .onClick(() => {
            // 方式1:通过名称跳转,可携带参数
            this.navStack.pushPath({ name: 'pageOne', param: '我是首页传来的参数' });
          })

        Button('跳转到 PageTwo (带回调)')
          .onClick(() => {
            // 方式2:带返回回调的跳转
            this.navStack.pushPathByName('pageTwo', '参数2', (popInfo) => {
              console.info('PageTwo 返回了:' + JSON.stringify(popInfo.result));
            });
          })

        Button('跳转到文章页 (传数组)')
          .onClick(() => {
            // 发送方逻辑:准备数组并序列化
            let titles: string[] = ["文章1", "文章2", "文章3"];
            // 将数组序列化为字符串传递
            this.navStack.pushPathByName("articlePage", JSON.stringify(titles));
          })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .mode(NavigationMode.Stack) // 设置为单栏模式
    .title('主标题')
    .hideTitleBar(false)
  }
}
TypeScript 复制代码
// src/main/ets/pages/ArticlePage.ets

// 1. 路由入口 Builder(必须加上,否则路由找不到页面)
@Builder
export function ArticlePageBuilder(name: string, param: Object) {
  // 将路由传来的参数传给组件的 initialData 属性
  ArticlePage({ initialData: param as string });
}

@Component
export struct ArticlePage {
  // 接收路由传来的 JSON 字符串
  initialData: string = '';

  @State buttonTitles: string[] = [];
  private navPathStack: NavPathStack = new NavPathStack();

  // 2. 在 aboutToAppear 中解析参数(比 onReady 更安全规范)
  aboutToAppear() {
    if (this.initialData) {
      try {
        this.buttonTitles = JSON.parse(this.initialData) as string[];
      } catch (e) {
        console.error('参数解析失败:', e);
      }
    }
  }

  build() {
    NavDestination() {
      Column() {
        Text('接收到的文章列表:').fontSize(24).margin({ bottom: 20 })
        ForEach(this.buttonTitles, (title: string) => {
          Button(title).margin(10)
        })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .title('文章页面')
    .onReady((ctx: NavDestinationContext) => {
      this.navPathStack = ctx.pathStack;
    })
  }
}
TypeScript 复制代码
{
  "routerMap": [
    {
      "name": "pageOne",
      "pageSourceFile": "src/main/ets/pages/PageOne.ets",
      "buildFunction": "PageOneBuilder"
    },
    {
      "name": "pageTwo",
      "pageSourceFile": "src/main/ets/pages/PageTwo.ets",
      "buildFunction": "PageTwoBuilder"
    },
    {
      "name": "articlePage",
      "pageSourceFile": "src/main/ets/pages/ArticlePage.ets",
      "buildFunction": "ArticlePageBuilder"
    }
  ]
}

三、 Router 模块:传统路由方式

Router 模块位于 @kit.ArkUI 中,适用于简单的页面跳转。虽然官方不再推荐作为首选,但在某些轻量级场景下依然有用。

1. 基础跳转

TypeScript 复制代码
import { router } from '@kit.ArkUI';

// 跳转到指定页面
router.pushUrl({
  url: 'pages/Detail',
  params: { id: 123 } // 传递参数
}, router.RouterMode.Standard);

2. 接收参数

TypeScript 复制代码
import { router } from '@kit.ArkUI';

@Entry
@Component
struct Detail {
  @State id: number = 0;

  aboutToAppear() {
    // 获取路由参数
    const params = router.getParams() as Record<string, number>;
    if (params) {
      this.id = params['id'];
    }
  }
}

3. 页面返回

TypeScript 复制代码
// 返回上一页
router.back();

// 返回指定页面
router.back({ url: 'pages/Index' });

四、 总结:Navigation 与 Router 对比
特性 Navigation (推荐) Router (不推荐)
路由容器 提供 Navigation 容器组件,支持标题栏、工具栏联动 无容器概念,基于页面栈管理
页面栈限制 无上限,支持无限跳转 最大 32 层,需手动清理
转场动画 支持自定义转场和共享元素动画 仅支持简单自定义动画
路由拦截 支持 setInterception 设置拦截 不支持
适用场景 复杂应用、多端适配、沉浸式页面 简单跳转、旧项目维护

最佳实践建议:

  • 新项目 :请统一使用 Navigation 组件,利用 NavPathStack 管理页面栈。
  • 参数传递 :传递复杂数据时,务必使用 JSON.stringifyJSON.parse 进行序列化与反序列化,避免引用丢失或属性名错乱。
  • 状态管理 :结合 AppStorageLocalStorage 可以在不同页面间共享状态,减少参数传递的复杂度。
相关推荐
Ww.xh2 小时前
鸿蒙WebView IPC防伪造请求方案
华为·harmonyos
大雷神4 小时前
第25篇|Surface 预览控制:ArkUI 页面如何接住相机画面
harmonyos
大雷神4 小时前
第24篇|相机权限和设备枚举:先判断能力再打开预览
harmonyos
Goway_Hui4 小时前
【鸿蒙原生应用开发--ArkUI--003】TodoApp - 待办事项应用教程
华为·harmonyos
想你依然心痛4 小时前
HarmonyOS 6(API 23)智能体驱动的沉浸式AR航天器装配工坊
华为·ar·harmonyos·智能体
不羁的木木5 小时前
HarmonyOS文件基础服务(Core File Kit)实战演练05-实战:文件管理工具开发
华为·harmonyos
Goway_Hui5 小时前
【鸿蒙原生应用开发--ArkUI--007】TimerApp - 计时器应用教程
华为·harmonyos
nashane5 小时前
HarmonyOS 6学习:保存图片预览空白?沙箱路径转URI的“视觉修复”术
学习·华为·harmonyos
芒鸽5 小时前
HarmonyOS ArkTS 状态管理深度解析:@State、@Prop、@Link、@Provide/@Consume 实战指南
华为·harmonyos·arkts·状态管理