ArkUI Navigation:页面一多就别只靠 router

举报
蓝瘦的蜕变 发表于 2026/09/18 23:04:43 2026/09/18
【摘要】 今天我们用 Navigation 做一个组件内的页面跳转。 刚开始写页面时,全局 router 的 pushUrl 很顺手。但当一个页面里面本来就有一组子页面,比如设置页、项目详情、二级说明页,一直用全局 router 跳来跳去,结构会越来越散。这个时候我们可以把这组子页交给 Navigation 管。 先准备一个导航

ArkUI Navigation:页面一多就别只靠 router

今天我们用 Navigation 做一个组件内的页面跳转。

刚开始写页面时,全局 routerpushUrl 很顺手。但当一个页面里面本来就有一组子页面,比如设置页、项目详情、二级说明页,一直用全局 router 跳来跳去,结构会越来越散。这个时候我们可以把这组子页交给 Navigation 管。

先准备一个导航栈

Navigation 需要一个 NavPathStack

private pathStack: NavPathStack = new NavPathStack()

这一步很关键。NavPathStack 不是全局对象,它跟着当前 Navigation 走。你可以把它理解成:这一组组件导航自己的页面栈。

用 navDestination 映射子页

我们在 Demo 里准备了两个目标页:

.navDestination(this.navigationMap)

navigationMap 根据传进来的 name 决定展示哪个 NavDestination。这篇先用单文件里的 builder 映射,省掉系统路由表和跨包路由这些额外内容。

点击后压入子页

按钮里调用:

this.pathStack.pushPathByName(item.routeName, new NavigationDemoPageParam(item.title, item.desc, item.tag))

这里和全局 router 的页面跳转不一样。pushPathByName 压入的是 NavDestination,不是 main_pages.json 里的 @Entry 页面。

另外记住几个边界:

  • router@Entry 页面跳转,Navigation 管组件内的 NavDestination
  • Navigation 花括号里的首页内容不在 NavPathStack 里,真正入栈的是子页。
  • 参数只放轻量数据,目标页仍然要准备缺省展示。
  • 每个 Navigation 自己持有自己的 NavPathStack,不要几个导航容器混着用一个来源不清楚的栈。

运行后看什么

打开这个案例页后,你会看到两个卡片。点击任意卡片,会进入对应的 NavDestination;在子页里点击按钮,会调用同一个 pathStack.pop() 回到导航首页。

你平时更常遇到哪种情况:全局页面跳转太多,还是一个页面内部子页太多?后面如果要继续写 Navigation 进阶,我会优先从这些真实场景里挑。

完整代码

class NavigationDemoTopic {
  routeName: string
  title: string
  desc: string
  tag: string
  color: string
  bgColor: string

  constructor(routeName: string, title: string, desc: string, tag: string, color: string, bgColor: string) {
    this.routeName = routeName
    this.title = title
    this.desc = desc
    this.tag = tag
    this.color = color
    this.bgColor = bgColor
  }
}

class NavigationDemoPageParam {
  title: string
  desc: string
  tag: string

  constructor(title: string, desc: string, tag: string) {
    this.title = title
    this.desc = desc
    this.tag = tag
  }
}

@Entry
@Component
struct ArkUINavigationDemo {
  private pathStack: NavPathStack = new NavPathStack()
  private topics: NavigationDemoTopic[] = [
    new NavigationDemoTopic('NavigationDetail', '项目详情', '进入一个 NavDestination,看看组件导航怎么压入子页。', 'pushPathByName', '#2563EB', '#EFF6FF'),
    new NavigationDemoTopic('NavigationRules', '导航边界', '把 router 和 Navigation 的职责分开,页面结构会清楚很多。', 'NavPathStack', '#0F766E', '#F0FDFA')
  ]

  @Builder
  navigationMap(name: string, param: Object) {
    if (name === 'NavigationDetail') {
      NavigationDetailPage({ param: param as NavigationDemoPageParam })
    } else if (name === 'NavigationRules') {
      NavigationRulesPage({ param: param as NavigationDemoPageParam })
    }
  }

  @Builder
  topicCard(item: NavigationDemoTopic) {
    Column({ space: 10 }) {
      Row() {
        Text(item.tag)
          .fontSize(12)
          .fontColor(item.color)
          .padding({ left: 10, right: 10, top: 4, bottom: 4 })
          .backgroundColor('#FFFFFF')
          .borderRadius(12)

        Blank()
      }
      .width('100%')

      Text(item.title)
        .fontSize(20)
        .fontWeight(700)
        .fontColor('#0F172A')

      Text(item.desc)
        .fontSize(14)
        .fontColor('#475569')
        .lineHeight(21)

      Button() {
        Text('进入子页')
          .fontSize(14)
          .fontColor('#FFFFFF')
      }
      .width(120)
      .height(38)
      .borderRadius(19)
      .backgroundColor(item.color)
      .onClick(() => {
        this.pathStack.pushPathByName(item.routeName, new NavigationDemoPageParam(item.title, item.desc, item.tag))
      })
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
    .padding(16)
    .backgroundColor(item.bgColor)
    .borderRadius(8)
  }

  @Builder
  noteRow(title: string, desc: string) {
    Row({ space: 10 }) {
      Text(title)
        .width(92)
        .fontSize(13)
        .fontWeight(700)
        .fontColor('#0F172A')

      Text(desc)
        .layoutWeight(1)
        .fontSize(13)
        .fontColor('#475569')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(VerticalAlign.Top)
  }

  build() {
    Navigation(this.pathStack) {
      Scroll() {
        Column({ space: 16 }) {
          Text('ArkUI Navigation')
            .width('100%')
            .fontSize(28)
            .fontWeight(800)
            .fontColor('#0F172A')

          Text('Navigation 是组件导航容器。它管理的是 NavDestination,不是 main_pages.json 里的 @Entry 页面。')
            .width('100%')
            .fontSize(14)
            .fontColor('#475569')
            .lineHeight(22)

          Column({ space: 10 }) {
            this.noteRow('导航首页', 'Navigation 花括号里的内容是导航首页,它自己不在 NavPathStack 里。')
            this.noteRow('子页入栈', '点击按钮后,pushPathByName 会把对应 NavDestination 压入这个 Navigation 的栈。')
            this.noteRow('职责边界', 'router 负责页面级跳转;Navigation 更适合页面内部的一组子页。')
          }
          .width('100%')
          .padding(14)
          .backgroundColor('#F8FAFC')
          .borderRadius(8)

          ForEach(this.topics, (item: NavigationDemoTopic) => {
            this.topicCard(item)
          }, (item: NavigationDemoTopic) => item.routeName)
        }
        .width('100%')
        .padding(20)
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#FFFFFF')
    }
    .title('Navigation')
    .titleMode(NavigationTitleMode.Mini)
    .mode(NavigationMode.Auto)
    .navDestination(this.navigationMap)
  }
}

@Component
struct NavigationDetailPage {
  param: NavigationDemoPageParam = new NavigationDemoPageParam('项目详情', '进入一个 NavDestination。', 'pushPathByName')
  private pathStack: NavPathStack = new NavPathStack()

  build() {
    NavDestination() {
      Column({ space: 16 }) {
        Text(this.param.tag)
          .fontSize(13)
          .fontColor('#2563EB')
          .padding({ left: 10, right: 10, top: 4, bottom: 4 })
          .backgroundColor('#EFF6FF')
          .borderRadius(12)

        Text(this.param.title)
          .fontSize(26)
          .fontWeight(800)
          .fontColor('#0F172A')

        Text(this.param.desc)
          .fontSize(15)
          .fontColor('#475569')
          .lineHeight(23)

        Text('这个页面不是一个新的 @Entry 页面,而是 Navigation 管理的 NavDestination。返回时调用同一个 pathStack.pop()。')
          .fontSize(14)
          .fontColor('#64748B')
          .lineHeight(22)
          .padding(14)
          .backgroundColor('#F8FAFC')
          .borderRadius(8)

        Button() {
          Text('返回导航首页')
            .fontSize(15)
            .fontColor('#FFFFFF')
        }
        .width('100%')
        .height(44)
        .borderRadius(22)
        .backgroundColor('#2563EB')
        .onClick(() => {
          this.pathStack.pop()
        })
      }
      .width('100%')
      .height('100%')
      .padding(20)
      .alignItems(HorizontalAlign.Start)
      .backgroundColor('#FFFFFF')
    }
    .title(this.param.title)
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack
    })
  }
}

@Component
struct NavigationRulesPage {
  param: NavigationDemoPageParam = new NavigationDemoPageParam('导航边界', '把职责分开。', 'NavPathStack')
  private pathStack: NavPathStack = new NavPathStack()

  @Builder
  ruleItem(title: string, desc: string) {
    Column({ space: 6 }) {
      Text(title)
        .fontSize(16)
        .fontWeight(700)
        .fontColor('#0F172A')
      Text(desc)
        .fontSize(13)
        .fontColor('#475569')
        .lineHeight(20)
    }
    .width('100%')
    .padding(14)
    .backgroundColor('#F8FAFC')
    .borderRadius(8)
  }

  build() {
    NavDestination() {
      Column({ space: 12 }) {
        Text(this.param.title)
          .fontSize(26)
          .fontWeight(800)
          .fontColor('#0F172A')

        this.ruleItem('一个 Navigation 一个栈', 'NavPathStack 跟着 Navigation 走,不要把几个导航容器混用一个不清楚来源的栈。')
        this.ruleItem('导航首页不入栈', 'Navigation 里的首页内容只是容器内容,push 后进入栈的是 NavDestination。')
        this.ruleItem('别和 router 混成一团', '这一组子页用 Navigation 管理;跨 @Entry 页面跳转再考虑 router。')

        Button() {
          Text('我知道了')
            .fontSize(15)
            .fontColor('#FFFFFF')
        }
        .width('100%')
        .height(44)
        .borderRadius(22)
        .backgroundColor('#0F766E')
        .onClick(() => {
          this.pathStack.pop()
        })
      }
      .width('100%')
      .height('100%')
      .padding(20)
      .alignItems(HorizontalAlign.Start)
      .backgroundColor('#FFFFFF')
    }
    .title(this.param.title)
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack
    })
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。