摘要

Router 是 ArkUI 实现多页面跳转、页面栈管理、参数传递的核心路由模块,支撑应用页面切换、返回上一页、页面替换、多页面传参、弹窗路由隔离等基础业务。API Version23 重构路由栈管理、页面参数序列化、生命周期跳转时序、页面销毁回收逻辑,修复低版本路由参数丢失、页面栈堆积内存泄漏、返回页面状态不刷新、replaceUrl 页面无法回退、复杂对象传参解析失败等兼容缺陷。低版本工程升级至 API23 后常出现:跳转后接收参数为空、多次 push 页面栈溢出、返回上一页数据未更新、对象数组路由传参丢失字段、路由跳转阻塞主线程卡顿等问题。本文基于 DevEco Studio 适配 OpenHarmony API23 及以上版本,系统讲解 pushUrl、replaceUrl、back、clear 等路由 API,基础参数 / 对象 / 数组传参、页面栈清理、返回传值、路由拦截、分页业务跳转实战,输出页面栈内存优化、参数序列化规范,汇总版本升级兼容故障解决方案,为鸿蒙多页面应用路由开发提供标准化实操模板。

关键词

OpenHarmony;ArkUI;API Version23;router;页面路由;页面传参;页面栈;路由跳转;返回传值

一、引言

1.1 Router 路由开发背景

单页面无法承载完整业务逻辑,绝大多数应用由首页、登录、设置、详情、编辑等多页面组成,依靠 Router 实现页面间导航与数据传递。路由分为临时页面跳转、替换当前页面、关闭页面返回、清空全部页面栈四类场景,同时支持简单字符串、数字、对象、数组参数跨页面传递。

OpenHarmony API Version23 针对路由底层栈引擎做全面升级,核心变更点:

  1. 重构页面栈生命周期调度,跳转 / 返回严格遵循页面销毁、重建时序;
  2. 强化路由参数序列化校验,复杂对象自动 JSON 序列化,修复字段丢失;
  3. 限制页面栈最大层级,连续 push 超过 15 页触发性能告警,防止内存溢出;
  4. 优化 back 返回页面状态恢复逻辑,支持返回时携带回传参数;
  5. 规范路由上下文隔离规则,弹窗、自定义组件内跳转无上下文丢失;
  6. 废弃同步阻塞跳转逻辑,全部路由操作异步非阻塞,不卡顿 UI。

大量 API9~11 旧项目升级后,详情页接收列表传递的对象为空、多次跳转无法返回首页、退出登录无法清空页面栈,因此掌握 API23 标准 Router 路由规范是多页面项目开发基础必修课。

1.2 开发环境与测试场景

开发工具:DevEco Studio 5.0 及以上 适配系统:OpenHarmony API Version23、HarmonyOS NEXT 开发语言:ArkTS 导入模块:@ohos.router 前置配置:pages.json 注册全部页面路径 测试场景:普通页面跳转、参数传递、替换页面、返回上一页、返回传参、清空页面栈、登录拦截路由、列表跳转详情页、退出登录清空栈

二、API23+ Router 核心 API 与版本变更说明

2.1 四大基础跳转 API

  1. router.pushUrl (options):新增页面入栈,可返回上一页(详情、编辑页常用)
  2. router.replaceUrl (options):替换当前页面,新页面替换旧页面,栈不新增层级(登录跳转首页)
  3. router.back (options):关闭当前页面,返回上一级页面,支持携带返回参数
  4. router.clear ():清空整个页面栈,仅保留当前页面(退出登录场景)

2.2 路由参数传递标准规则(API23 强制序列化)

  • 简单基础类型:string、number、boolean 可直接传入 params
  • 对象 / 数组:底层自动 JSON 序列化传递,接收页面直接解析使用
  • API23 修复:复杂嵌套对象不会丢失子属性,低版本多层对象会截断字段

2.3 页面参数接收方式

页面内通过router.getParams()读取跳转传入参数,页面 aboutToAppear 生命周期读取,每次进入页面自动刷新参数。

2.4 API23 废弃与强制约束

  1. 废弃无参简单字符串跳转router.pushUrl("pages/index/index"),必须传入完整 options 对象;
  2. 禁止超大数组、二进制大图对象路由传参,超过 2000 字符触发性能告警;
  3. 页面栈连续 push 不得超过 15 层,循环跳转必须搭配 replace 或 clear 清理栈;
  4. 废弃全局共享路由变量,每个页面独立调用 getParams 读取自有参数;
  5. back 返回时若携带复杂对象,必须手动判断参数是否存在,避免空值报错。

三、API23 标准基础示例代码

3.1 pushUrl 带基础参数跳转

ets

import router from '@ohos.router'

// 跳转详情页,传递数字ID与标题文本
router.pushUrl({
  url: "pages/detail/detail",
  params: {
    id: 1001,
    title: "API23路由实战"
  }
})

3.2 目标页面读取路由参数

ets

import router from '@ohos.router'

@Entry
@Component
struct DetailPage {
  @State id: number = 0
  @State title: string = ""

  aboutToAppear() {
    let params = router.getParams()
    this.id = params?.id ?? 0
    this.title = params?.title ?? "默认标题"
  }

  build() {
    Column() {
      Text("接收路由ID:" + this.id)
      Text("接收标题:" + this.title)
      Button("返回列表页").onClick(()=>router.back())
    }
  }
}

3.3 replaceUrl 替换页面(登录成功跳转首页,无法回退登录页)

ets

router.replaceUrl({
  url: "pages/index/index",
  params: { isLogin: true }
})

3.4 back 返回并携带回传参数

ets

// 编辑页保存后返回列表,携带修改标识
router.back({
  params: { updateFlag: true }
})

3.5 清空页面栈(退出登录)

ets

router.clear()
router.replaceUrl({ url: "pages/login/login" })

四、五大业务完整实战案例(全兼容 API23)

4.1 实战一:列表页面跳转详情(对象传参)

  1. 列表页 pages/list/list

ets

import router from '@ohos.router'

interface GoodsItem {
  id: number
  name: string
  price: number
}

@Entry
@Component
struct ListPage {
  private goodsList: GoodsItem[] = [
    { id: 1, name: "鸿蒙API23开发教程", price: 49.9 },
    { id: 2, name: "ArkUI组件实战合集", price: 39.9 }
  ]

  build() {
    List({ space: 12 }) {
      ForEach(this.goodsList, (item:GoodsItem) => {
        ListItem() {
          Row() {
            Column().layoutWeight(1) {
              Text(item.name).fontSize(17)
              Text(`¥${item.price}`).fontColor("#f56c6c")
            }
            Button("查看详情")
              .onClick(() => {
                // 传递完整商品对象
                router.pushUrl({
                  url: "pages/detail/detail",
                  params: { goods: item }
                })
              })
          }
          .width("100%")
          .padding(16)
          .backgroundColor(Color.White)
          .borderRadius(10)
        })
      })
    }
    .width("100%")
    .height("100%")
    .padding(15)
    .backgroundColor("#F5F5F5")
  }
}
  1. 详情页 pages/detail/detail

ets

import router from '@ohos.router'

interface GoodsItem {
  id: number
  name: string
  price: number
}

@Entry
@Component
struct DetailPage {
  @State goods: GoodsItem | null = null

  aboutToAppear() {
    let params = router.getParams()
    if(params?.goods) {
      this.goods = params.goods
    }
  }

  build() {
    Column({ space: 20 }) {
      if(this.goods) {
        Text("商品详情").fontSize(24).fontWeight(FontWeight.Bold)
        Text("编号:" + this.goods.id).fontSize(16)
        Text("名称:" + this.goods.name).fontSize(16)
        Text("价格:¥" + this.goods.price).fontSize(18).fontColor("#f56c6c")
      } else {
        Text("参数加载失败").fontColor("#999")
      }
      Button("返回列表").width(200).height(46)
        .onClick(()=>router.back())
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

4.2 实战二:编辑页面返回列表携带更新标识(back 回传参数)

  1. 编辑页 pages/edit/edit

ets

import router from '@ohos.router'

@Entry
@Component
struct EditPage {
  @State content: string = ""

  // 保存后返回,传递更新标记
  saveAndBack() {
    // 模拟保存逻辑
    router.back({
      params: { refresh: true }
    })
  }

  build() {
    Column({ space: 25 }) {
      TextInput({ text: this.content, placeholder:"输入编辑内容" })
        .width("100%").height(48)
      Row({ space: 15 }) {
        Button("取消返回").layoutWeight(1).onClick(()=>router.back())
        Button("保存并返回").layoutWeight(1).backgroundColor("#007DFF")
          .onClick(()=>this.saveAndBack())
      }
    }
    .padding(20)
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}
  1. 列表页接收返回参数,刷新数据

ets

import router from '@ohos.router'
import rdbUtil from '../utils/rdb_util'

@Entry
@Component
struct NoteListPage {
  @State list: any[] = []

  async aboutToAppear() {
    // 读取返回参数
    let params = router.getParams()
    if(params?.refresh) {
      // 有更新标记,重新加载数据库数据
      await this.loadData()
    }
  }

  async loadData() {
    this.list = await rdbUtil.queryNoteList(0,10)
  }

  build() {
    Column() {
      Button("前往编辑页").onClick(()=>router.pushUrl({url:"pages/edit/edit"}))
      List({space:10}) {
        ForEach(this.list, item=>ListItem(){Text(item.title).padding(16)})
      }
    }
  }
}

4.3 实战三:登录页面跳转首页(replaceUrl 不保留登录页面)

ets

import router from '@ohos.router'
import prefUtil from '../utils/preference_utils'

@Entry
@Component
struct LoginPage {
  @State account: string = ""

  async handleLogin() {
    // 模拟登录逻辑,持久化存储账号
    await prefUtil.putString("user_name", this.account)
    AppStorage.Set("isLogin", true)
    // replace替换当前登录页面,栈内无登录记录,无法返回
    router.replaceUrl({
      url: "pages/index/index",
      params: { loginUser: this.account }
    })
  }

  build() {
    Column({ space:22 }) {
      Text("用户登录").fontSize(26).fontWeight(FontWeight.Bold)
      TextInput({text:this.account, placeholder:"账号"}).width("100%").height(48)
      Button("登录进入首页").width("100%").height(48).backgroundColor("#007DFF")
        .onClick(async ()=>await this.handleLogin())
    }
    .padding(25)
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

4.4 实战四:退出登录清空页面栈(router.clear)

ets

import router from '@ohos.router'

@Entry
@Component
struct MinePage {
  @StorageLink('isLogin') loginState: boolean = false

  logout() {
    // 清空全局登录状态、本地存储
    AppStorage.Clear()
    prefUtil.clearAll()
    // 清空全部页面栈,跳转登录页
    router.clear()
    router.replaceUrl({ url: "pages/login/login" })
  }

  build() {
    Column() {
      Text("个人中心页面").fontSize(22)
      Button("退出登录").width(220).height(48).backgroundColor("#f56c6c")
        .onClick(()=>this.logout())
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

4.5 实战五:路由拦截,未登录强制跳转登录页

封装全局跳转工具,跳转前判断登录态,未登录拦截至登录页

ets

// utils/router_util.ets
import router from '@ohos.router'
import AppStorage from '@ohos.arkui.state.AppStorage'

class RouterUtil {
  static push(url: string, params?: Object) {
    let isLogin = AppStorage.Get<boolean>("isLogin") ?? false
    if(!isLogin) {
      // 未登录拦截,跳转登录
      router.replaceUrl({url:"pages/login/login"})
      return
    }
    router.pushUrl({ url, params })
  }

  static replace(url: string, params?: Object) {
    router.replaceUrl({ url, params })
  }

  static back(params?: Object) {
    router.back({ params })
  }

  static clearAndLogin() {
    router.clear()
    router.replaceUrl({url:"pages/login/login"})
  }
}

export default RouterUtil

调用示例:

ets

import RouterUtil from '../utils/router_util'
// 跳转需要登录的设置页,未登录自动拦截
RouterUtil.push("pages/setting/setting")

五、API23+ 路由栈适配与内存优化规范

5.1 页面栈层级规范

  1. 列表→详情→编辑使用 pushUrl,正常多层回退;
  2. 登录→首页、支付成功→订单列表使用 replaceUrl,销毁前置页面减少栈层级;
  3. 退出登录执行 clear 清空全部栈,避免大量页面驻留内存;
  4. 业务循环跳转限制在 3 层以内,超过 15 层 API23 输出性能告警。

5.2 参数传递规范

  1. 小型标识(id、开关)使用基础类型;详情完整数据传递对象;
  2. 禁止传递超大长文本、二进制图片、DOM 组件实例;
  3. 目标页面读取参数增加空值判断,使用??默认值兜底防止报错;
  4. back 返回传参仅传递布尔刷新标记、简单 ID,不传递完整大对象。

5.3 性能优化准则

  1. 频繁列表跳转详情,统一使用 ID 路由传参,数据库二次查询替代完整对象;
  2. 页面 aboutToDisappear 取消页面内定时器、网络请求,减少栈内资源占用;
  3. 弹窗、自定义组件内跳转统一封装路由工具,避免上下文丢失;
  4. 不需要返回的页面全部使用 replaceUrl,控制页面栈数量。

六、API23 升级高频兼容问题与解决方案

问题 1:跳转后 getParams 读取参数为空,对象字段丢失 解决:API23 必须完整传入 options 对象,不能简写 url 字符串;多层嵌套对象由系统序列化,读取前做空值判断。

问题 2:连续多次 push 页面,返回时需要多次点击 back 才能回到首页 解决:非必要页面使用 replaceUrl 替换,退出登录调用 clear 清空页面栈。

问题 3:编辑页保存 back 返回列表,列表数据不刷新 解决:back 携带 refresh 布尔参数,列表页 aboutToAppear 读取参数判断是否重载数据。

问题 4:旧版简写 router.push ("pages/index") 编译报错 解决:全部替换为标准 options 写法pushUrl({url:"xxx", params:{}})

问题 5:未登录直接访问需要权限页面,空白无响应 解决:封装路由工具统一拦截,读取 AppStorage 登录状态,未登录强制跳转登录页。

问题 6:页面大量跳转后应用内存持续升高、卡顿 解决:控制页面栈深度,多余页面使用 replace/clear 销毁,页面销毁释放定时器、数据库游标等资源。

七、总结

Router 路由是多页面应用导航核心模块,API Version23 重构页面栈生命周期、参数序列化、跳转异步逻辑,规范 push/replace/back/clear 四类跳转适用场景,修复低版本参数丢失、栈溢出、页面状态不刷新等核心缺陷。项目开发分层路由策略:普通详情页 push、登录切换首页 replace、退出登录 clear 清空栈、返回页面携带刷新标记实现数据重载,搭配路由工具类统一登录拦截,大幅减少重复代码。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐