当前位置: 代码网 > it编程>编程语言>其他编程 > 鸿蒙Router与Navigation组件导航示例详解

鸿蒙Router与Navigation组件导航示例详解

2026年07月16日 其他编程 我要评论
适用版本:harmonyos next / api 12+语言:arkts一、两种导航方案概览鸿蒙提供了两套页面导航方案,适用于不同场景:对比维度router(路由)navigation(组件导航)出

适用版本:harmonyos next / api 12+
语言:arkts

一、两种导航方案概览

鸿蒙提供了两套页面导航方案,适用于不同场景:

对比维度router(路由)navigation(组件导航)
出现版本早期版本api 9+,api 12 成熟
导航粒度页面级(整页跳转)组件级(灵活嵌套)
栈管理系统统一管理开发者手动管理 navpathstack
传参方式路由参数 paramsnavpathstack 携带强类型参数
适配多端手机较好,平板一般原生支持响应式(手机/平板/折叠屏)
生命周期onpageshow / onpagehideonready / onshown / onhidden
动画系统默认动画完全自定义转场动画
推荐程度旧项目维护新项目首选

二、router:传统页面路由

2.1 基本配置

router 基于 文件路径 做路由,页面必须在 main_pages.json 中注册。

main_pages.json

{
  "src": [
    "pages/index",
    "pages/detailpage",
    "pages/profilepage",
    "pages/loginpage"
  ]
}

每个页面的 .ets 文件必须加 @entry 装饰器:

// pages/detailpage.ets
@entry
@component
struct detailpage {
  build() {
    column() {
      text('详情页')
    }
  }
}

2.2 页面跳转

import { router } from '@ohos.router';

// ① 普通跳转(可返回)
router.pushurl({
  url: 'pages/detailpage',
  params: {
    id: '123',
    title: '商品详情',
  }
});

// ② 替换当前页(不可返回)
router.replaceurl({
  url: 'pages/loginpage',
});

// ③ 跳转并清空历史栈
router.pushurl(
  { url: 'pages/index' },
  router.routermode.single // 单实例模式:若栈中已有该页则移到顶部
);

2.3 接收参数

// pages/detailpage.ets
import { router } from '@ohos.router';

interface detailparams {
  id: string;
  title: string;
}

@entry
@component
struct detailpage {
  private params: detailparams = router.getparams() as detailparams;

  build() {
    column() {
      text(`id: ${this.params.id}`)
      text(`标题: ${this.params.title}`)
    }
  }
}

2.4 返回与返回传值

// 简单返回
router.back();

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

// 返回并带数据(通过 params,接收方在 onpageshow 中获取)
router.back({
  url: 'pages/orderpage',
  params: { refreshlist: true }
});
// orderpage.ets 在 onpageshow 中接收返回数据
@entry
@component
struct orderpage {
  onpageshow() {
    const params = router.getparams() as record<string, boolean>;
    if (params?.refreshlist) {
      this.loadorders();
    }
  }

  loadorders() { /* ... */ }

  build() { /* ... */ }
}

2.5 router 完整案例:商品列表 → 详情

列表页 productlistpage.ets

import { router } from '@ohos.router';

interface product {
  id: string;
  name: string;
  price: number;
}

@entry
@component
struct productlistpage {
  private products: product[] = [
    { id: '1', name: '苹果', price: 5 },
    { id: '2', name: '橙子', price: 8 },
    { id: '3', name: '葡萄', price: 15 },
  ];

  build() {
    column() {
      text('商品列表').fontsize(20).fontweight(fontweight.bold).margin(16)

      list({ space: 12 }) {
        foreach(this.products, (item: product) => {
          listitem() {
            row() {
              text(item.name).fontsize(16).layoutweight(1)
              text(`¥${item.price}`).fontsize(16).fontcolor('#ff5722')
            }
            .width('100%')
            .padding(16)
            .backgroundcolor(color.white)
            .borderradius(8)
          }
          .onclick(() => {
            router.pushurl({
              url: 'pages/productdetailpage',
              params: { id: item.id, name: item.name, price: item.price }
            });
          })
        })
      }
      .width('100%')
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundcolor('#f5f5f5')
  }
}

详情页 productdetailpage.ets

import { router } from '@ohos.router';

interface productparams {
  id: string;
  name: string;
  price: number;
}

@entry
@component
struct productdetailpage {
  private product: productparams = router.getparams() as productparams;
  @state incart: boolean = false;

  build() {
    column() {
      // 顶部导航栏
      row() {
        image($r('app.media.ic_back'))
          .width(24).height(24)
          .onclick(() => router.back())
        text('商品详情').fontsize(18).fontweight(fontweight.medium).margin({ left: 16 })
      }
      .width('100%').height(56).padding({ left: 16 })

      // 内容
      column({ space: 12 }) {
        text(this.product.name).fontsize(24).fontweight(fontweight.bold)
        text(`¥${this.product.price}`).fontsize(20).fontcolor('#ff5722')
        text('商品描述:这是一款优质的新鲜水果,产地直供。').fontsize(14).fontcolor('#666')
      }
      .width('100%').padding(16).alignitems(horizontalalign.start)

      // 加入购物车
      button(this.incart ? '已加入购物车' : '加入购物车')
        .width('90%')
        .backgroundcolor(this.incart ? '#999' : '#ff5722')
        .onclick(() => {
          this.incart = true;
          // 返回时通知列表页刷新购物车数量
          router.back({
            url: 'pages/productlistpage',
            params: { cartupdated: true }
          });
        })
    }
    .width('100%').height('100%')
  }
}

2.6 router 的局限性

  1. 必须注册页面路径,动态路由不方便
  2. 参数是 object 类型,没有 typescript 类型推断
  3. 平板适配差,无法天然实现左右分栏
  4. 无法细粒度控制转场动画
  5. 返回传值麻烦,需要在 onpageshow 中手动获取

三、navigation:组件化导航(推荐)

3.1 核心概念

navigation 由三个核心部分组成:

navigation(导航容器)
    │
    ├── navpathstack(路由栈,由开发者创建和管理)
    │
    └── navdestination(导航目标页,替代 @entry 页面)
            └── 通过 navdestination builder 动态创建

关键特点:

  • navpathstack 是强类型的路由栈,可携带任意类型参数
  • navdestination 是组件,不需要在 main_pages.json 注册
  • 支持响应式布局(navigationmode.auto 自动切换单/双栏)

3.2 最简单的 navigation 示例

// index.ets(入口页)
@entry
@componentv2
struct index {
  // ① 创建并管理路由栈
  private stack: navpathstack = new navpathstack();

  build() {
    // ② navigation 作为根容器,绑定路由栈
    navigation(this.stack) {
      // 主页内容
      column() {
        text('首页').fontsize(24)
        button('跳转详情页')
          .onclick(() => {
            // ③ 通过路由栈跳转
            this.stack.pushpathbyname('detailpage', { id: '123' });
          })
      }
      .width('100%').height('100%').justifycontent(flexalign.center)
    }
    // ④ 注册所有目标页面
    .navdestination(pagemap)
    .mode(navigationmode.stack)
    .hidetitlebar(true)
  }
}

// ⑤ 路由映射表(builder 函数)
@builder
function pagemap(name: string, param: object) {
  if (name === 'detailpage') {
    detailpage({ param: param as detailparam })
  }
}
// detailpage.ets(目标页)
interface detailparam {
  id: string;
}

@componentv2
struct detailpage {
  @param param: detailparam = { id: '' };
  // 通过 @consumer 获取路由栈(可选,也可通过 navdestinationcontext)
  @consumer() stack: navpathstack = new navpathstack();

  build() {
    navdestination() {
      column() {
        text(`详情 id: ${this.param.id}`)
        button('返回')
          .onclick(() => this.stack.pop())
      }
      .width('100%').height('100%').justifycontent(flexalign.center)
    }
    .title('详情页')
  }
}

3.3 navpathstack 常用 api

const stack = new navpathstack();

// === 跳转 ===
stack.pushpathbyname('pagename', params);       // 压栈跳转
stack.pushpathbyname('pagename', params, false); // 跳转但不加动画
stack.replacepath({ name: 'loginpage' });        // 替换当前页(不可返回)
stack.pushpath({ name: 'pagename', param: {} }); // 使用 navpathinfo 跳转

// === 返回 ===
stack.pop();                    // 返回上一页
stack.pop({ result: 'ok' });    // 返回并携带结果
stack.poptoname('homepage');    // 返回到指定页面
stack.poptoindex(0);            // 返回到栈底
stack.clear();                  // 清空路由栈

// === 查询 ===
stack.size();                   // 栈的深度
stack.getallpathname();          // 获取所有页面名称列表
stack.getparambyname('pagename'); // 获取某页面的参数

3.4 接收返回值

navigation 支持优雅的返回值回调,不需要像 router 那样在 onpageshow 中判断:

// 跳转时注册回调
stack.pushpathbyname('editpage', { userid: '123' }, (popinfo) => {
  // 当 editpage 调用 stack.pop(result) 时,这里收到返回值
  const result = popinfo.result as editresult;
  if (result.saved) {
    this.loaduserinfo(); // 刷新数据
  }
});
// editpage 返回时携带结果
@componentv2
struct editpage {
  @consumer() stack: navpathstack = new navpathstack();

  build() {
    navdestination() {
      button('保存并返回')
        .onclick(() => {
          // pop 时传入结果
          this.stack.pop({ saved: true, nickname: '新昵称' });
        })
    }
  }
}

3.5 完整案例:电商 app 导航架构

这是一个完整的三层导航案例:首页 → 商品列表 → 商品详情。

第一步:定义路由常量和参数类型

// constants/routenames.ets
export const routenames = {
  product_list: 'productlistpage',
  product_detail: 'productdetailpage',
  order_confirm: 'orderconfirmpage',
  login: 'loginpage',
};

// 各页面参数类型
export interface productlistparam {
  categoryid: string;
  categoryname: string;
}

export interface productdetailparam {
  productid: string;
  productname: string;
  price: number;
}

export interface orderconfirmparam {
  productid: string;
  quantity: number;
}

第二步:创建路由映射(集中管理)

// router/pagemap.ets
import { routenames, productlistparam, productdetailparam, orderconfirmparam } from '../constants/routenames';
import { productlistpage } from '../pages/productlistpage';
import { productdetailpage } from '../pages/productdetailpage';
import { orderconfirmpage } from '../pages/orderconfirmpage';
import { loginpage } from '../pages/loginpage';

@builder
export function apppagemap(name: string, param: object) {
  if (name === routenames.product_list) {
    productlistpage({ param: param as productlistparam })
  } else if (name === routenames.product_detail) {
    productdetailpage({ param: param as productdetailparam })
  } else if (name === routenames.order_confirm) {
    orderconfirmpage({ param: param as orderconfirmparam })
  } else if (name === routenames.login) {
    loginpage()
  }
}

第三步:入口页(首页)

// pages/index.ets
import { apppagemap } from '../router/pagemap';
import { routenames, productlistparam } from '../constants/routenames';

@entry
@componentv2
struct index {
  @provider() stack: navpathstack = new navpathstack();

  private categories = [
    { id: 'fruit', name: '水果生鲜' },
    { id: 'snack', name: '零食饮料' },
    { id: 'daily', name: '日用百货' },
  ];

  build() {
    navigation(this.stack) {
      scroll() {
        column({ space: 16 }) {
          text('首页').fontsize(24).fontweight(fontweight.bold)

          // 分类入口
          foreach(this.categories, (cat: { id: string; name: string }) => {
            row() {
              text(cat.name).fontsize(16).layoutweight(1)
              image($r('app.media.ic_arrow_right')).width(16).height(16)
            }
            .width('100%').padding(16)
            .backgroundcolor(color.white).borderradius(8)
            .onclick(() => {
              const param: productlistparam = {
                categoryid: cat.id,
                categoryname: cat.name,
              };
              this.stack.pushpathbyname(routenames.product_list, param);
            })
          })
        }
        .padding(16)
      }
    }
    .navdestination(apppagemap)
    .mode(navigationmode.stack)
    .hidetitlebar(true)
    .width('100%').height('100%')
  }
}

第四步:商品列表页

// pages/productlistpage.ets
import { routenames, productlistparam, productdetailparam } from '../constants/routenames';

interface product {
  id: string;
  name: string;
  price: number;
}

@componentv2
export struct productlistpage {
  @param param: productlistparam = { categoryid: '', categoryname: '' };
  @consumer() stack: navpathstack = new navpathstack();

  // 模拟数据
  private getproducts(): product[] {
    return [
      { id: 'p1', name: `${this.param.categoryname}-商品a`, price: 19.9 },
      { id: 'p2', name: `${this.param.categoryname}-商品b`, price: 35.5 },
      { id: 'p3', name: `${this.param.categoryname}-商品c`, price: 8.8 },
    ];
  }

  build() {
    navdestination() {
      list({ space: 12 }) {
        foreach(this.getproducts(), (product: product) => {
          listitem() {
            row() {
              column({ space: 4 }) {
                text(product.name).fontsize(16)
                text(`¥${product.price.tofixed(2)}`).fontsize(14).fontcolor('#ff5722')
              }
              .alignitems(horizontalalign.start).layoutweight(1)

              button('查看详情').fontsize(12)
                .onclick(() => {
                  const param: productdetailparam = {
                    productid: product.id,
                    productname: product.name,
                    price: product.price,
                  };
                  // 跳转详情并接收返回值
                  this.stack.pushpathbyname(routenames.product_detail, param, (popinfo) => {
                    const result = popinfo.result as { addedtocart: boolean };
                    if (result?.addedtocart) {
                      console.info('用户已加入购物车,刷新角标');
                    }
                  });
                })
            }
            .width('100%').padding(16)
            .backgroundcolor(color.white).borderradius(8)
          }
        })
      }
      .padding(16)
    }
    .title(this.param.categoryname)
    .backgroundcolor('#f5f5f5')
  }
}

第五步:商品详情页

// pages/productdetailpage.ets
import { routenames, productdetailparam, orderconfirmparam } from '../constants/routenames';

@componentv2
export struct productdetailpage {
  @param param: productdetailparam = { productid: '', productname: '', price: 0 };
  @consumer() stack: navpathstack = new navpathstack();
  @local quantity: number = 1;

  build() {
    navdestination() {
      column({ space: 0 }) {

        // 商品信息区
        column({ space: 12 }) {
          text(this.param.productname)
            .fontsize(22).fontweight(fontweight.bold)
          text(`¥${this.param.price.tofixed(2)}`)
            .fontsize(20).fontcolor('#ff5722')
          text('产地直供,新鲜直达,品质保证。')
            .fontsize(14).fontcolor('#888').lineheight(22)
        }
        .width('100%').padding(16).alignitems(horizontalalign.start)
        .backgroundcolor(color.white)

        // 数量选择
        row({ space: 16 }) {
          text('数量').fontsize(15)
          row({ space: 12 }) {
            button('-').width(32).height(32)
              .onclick(() => { if (this.quantity > 1) this.quantity--; })
            text(`${this.quantity}`).fontsize(16).width(40).textalign(textalign.center)
            button('+').width(32).height(32)
              .onclick(() => { this.quantity++; })
          }
        }
        .width('100%').padding(16).justifycontent(flexalign.spacebetween)
        .backgroundcolor(color.white).margin({ top: 8 })

        blank()

        // 底部按钮区
        row({ space: 12 }) {
          button('加入购物车')
            .layoutweight(1).backgroundcolor('#ff9800')
            .onclick(() => {
              // 返回并携带结果给列表页
              this.stack.pop({ addedtocart: true });
            })

          button('立即购买')
            .layoutweight(1).backgroundcolor('#ff5722')
            .onclick(() => {
              const param: orderconfirmparam = {
                productid: this.param.productid,
                quantity: this.quantity,
              };
              this.stack.pushpathbyname(routenames.order_confirm, param);
            })
        }
        .width('100%').padding(16)
        .backgroundcolor(color.white)
        .border({ width: { top: 1 }, color: '#f0f0f0' })
      }
      .width('100%').height('100%').backgroundcolor('#f5f5f5')
    }
    .title(this.param.productname)
  }
}

3.6 navigationmode 响应式布局(平板适配)

navigation 最大的优势之一是原生支持响应式双栏布局,一套代码适配手机和平板。

@entry
@componentv2
struct index {
  @provider() stack: navpathstack = new navpathstack();

  build() {
    navigation(this.stack) {
      // 左栏:分类列表(平板模式下常驻)
      categorylist()
    }
    .navdestination(apppagemap)
    // auto 模式:屏幕宽 < 520vp 单栏,>= 520vp 自动双栏
    .mode(navigationmode.auto)
    // 双栏时左栏宽度
    .navbarwidth(300)
    .navbarwidthrange([240, 400])
    .hidetitlebar(true)
  }
}
模式说明
navigationmode.stack始终单栏(手机常用)
navigationmode.split始终双栏(横屏平板)
navigationmode.auto自动根据屏幕宽度切换

3.7 自定义转场动画

navigation 支持完全自定义的转场效果:

// 入场动画(从右侧滑入)
stack.pushpathbyname('detailpage', param);

// 在 navdestination 中配置动画
navdestination() {
  // ...
}
.customnavcontenttransition((from, to, operation) => {
  // operation: navigationoperation.push / pop / replace
  const isforward = operation === navigationoperation.push;
  return {
    ontransitionend: (issuccess: boolean) => {},
    timeout: 1000,
    transition: (proxy: navigationtransitionproxy) => {
      // 自定义动画逻辑
      animateto({ duration: 300, curve: curve.easeout }, () => {
        proxy.finishtransition();
      });
    }
  };
})

3.8 navdestination 生命周期

navdestination 有独立的生命周期,比 router 页面更精细:

navdestination() {
  // ...
}
.onready((ctx: navdestinationcontext) => {
  // 页面创建完成,可获取参数和路由栈
  const param = ctx.pathinfo.param;
  const stack = ctx.pathstack;
})
.onshown(() => {
  // 页面出现在前台(包括从下级页面返回)
  console.info('页面可见,刷新数据');
})
.onhidden(() => {
  // 页面被遮挡(跳转到下级页面时)
  console.info('页面不可见,暂停动画');
})
.onbackpressed(() => {
  // 拦截返回键,返回 true 表示自行处理(不执行默认返回)
  if (this.hasunsavedchanges) {
    this.showsavedialog();
    return true;
  }
  return false; // false = 执行默认返回行为
})

四、router vs navigation 对比总结

4.1 参数传递对比

router(类型不安全)

// 发送方
router.pushurl({
  url: 'pages/detail',
  params: { id: '123' } // object 类型,无类型约束
});

// 接收方(需要手动断言,容易出错)
const params = router.getparams() as { id: string };

navigation(强类型)

// 发送方
interface detailparam { id: string; }
stack.pushpathbyname('detailpage', { id: '123' } as detailparam);

// 接收方(通过 @param 直接接收,完全类型安全)
@componentv2
struct detailpage {
  @param param: detailparam = { id: '' };
}

4.2 返回值对比

router(繁琐)

// 返回方(只能夹带在 params 里)
router.back({ url: 'pages/list', params: { refreshed: true } });

// 接收方(需要在 onpageshow 里判断)
onpageshow() {
  const p = router.getparams() as { refreshed?: boolean };
  if (p?.refreshed) this.refresh();
}

navigation(优雅)

// 跳转时直接注册回调
stack.pushpathbyname('editpage', param, (popinfo) => {
  const result = popinfo.result as { refreshed: boolean };
  if (result.refreshed) this.refresh();
});

// 返回方
stack.pop({ refreshed: true });

4.3 适用场景选择

新项目?
    └── 是 → 直接用 navigation ✅

旧项目?
    ├── 页面不多,维护即可 → 继续用 router
    └── 需要平板适配 / 复杂动画 → 逐步迁移到 navigation

需要平板双栏布局?
    └── 必须用 navigation(navigationmode.auto)

需要拦截返回键?
    ├── router → onbackpress() 只能全局处理
    └── navigation → navdestination.onbackpressed() 精细控制

页面层级深(3层以上)?
    └── 推荐 navigation(navpathstack 管理更清晰)

五、混用方案(过渡期)

如果项目中已有大量 router 代码,短期内可以混用,但需注意:

// 在 navigation 体系中调用 router 跳转到旧页面
import { router } from '@ohos.router';

@componentv2
struct somepage {
  @consumer() stack: navpathstack = new navpathstack();

  build() {
    navdestination() {
      button('跳转旧页面(router)')
        .onclick(() => {
          // 新旧混用,谨慎使用
          router.pushurl({ url: 'pages/oldpage' });
        })
    }
  }
}

⚠️ 混用注意事项:

  1. router 和 navigation 各自维护独立的路由栈,router.back() 不会影响 navpathstack
  2. 混用期间动画可能不一致
  3. 建议新功能全部用 navigation,旧页面逐步迁移

六、常见坑点

坑 1:navdestination 不写 @param 导致参数丢失

// ❌ 错误:用普通属性接收,首次渲染时参数可能未初始化
@componentv2
struct detailpage {
  param: detailparam = { id: '' }; // 没有 @param
}

// ✅ 正确:必须用 @param 装饰器
@componentv2
struct detailpage {
  @param param: detailparam = { id: '' };
}

坑 2:路由栈没有用 @provider 导致子组件拿不到

// ❌ 错误:普通属性不会被 @consumer 接收
struct index {
  private stack: navpathstack = new navpathstack(); // 普通属性
}

// ✅ 正确:用 @provider 使子组件可以 @consumer 接收
struct index {
  @provider() stack: navpathstack = new navpathstack();
}

坑 3:多个 navigation 嵌套导致路由混乱

// ❌ 错误:index 和 detailpage 各自有 navigation,会产生两个路由栈
struct index {
  build() {
    navigation(this.stack) {
      // ...
    }
  }
}
struct detailpage {
  build() {
    navdestination() {
      navigation(anotherstack) { // ❌ 多余的嵌套 navigation
        // ...
      }
    }
  }
}

// ✅ 正确:全局只有一个根 navigation,子页面用 navdestination

坑 4:忘记在 navdestination 中注册页面

// ❌ 错误:跳转后白屏
navigation(this.stack) { /* ... */ }
// 没有 .navdestination(pagemap)

// ✅ 正确:必须绑定 pagemap
navigation(this.stack) { /* ... */ }
.navdestination(pagemap)

七、总结

场景推荐方案
新项目navigation(全面使用)
旧项目维护router(不动现有代码)
需要平板/折叠屏适配navigation(auto 模式)
需要强类型参数navigation(@param)
需要优雅的返回值navigation(pop 回调)
需要精细控制生命周期navigation(onshown/onhidden)
简单的几个页面跳转router 或 navigation 均可

一句话总结: router 是「跳页面」,navigation 是「管状态」——navigation 把整个应用的页面流转统一纳入 navpathstack 管理,是鸿蒙官方力推的现代导航方案,新项目直接用就对了。

到此这篇关于鸿蒙router与navigation组件导航的文章就介绍到这了,更多相关鸿蒙router与navigation组件导航内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com