HarmonyOS NEXT 导航最佳实践:用 Navigation + NavPathStack 接管页面栈
HarmonyOS NEXT 导航最佳实践:用 Navigation + NavPathStack 接管页面栈
前言
在 HarmonyOS NEXT(API 9+)中,系统官方推荐使用 Navigation 组件作为应用内的主导航容器,逐步取代旧版 @ohos.router 的全局跳转。相比 Router,Navigation 把页面栈(NavPathStack)收拢到组件内部,支持路由表、参数类型安全、拦截返回、转场动画等能力,更适合中大型应用的分层架构。本文从一次真实的多页面跳转改造入手,讲清 Navigation 的核心机制与常见坑。
问题描述
开发者在迁移到 NEXT 时常遇到下面几类问题:
- 用
router.pushUrl跳转后,目标页拿不到复杂参数,只能塞params: { id: 1 }这种扁平对象,类型完全丢失。 - 想在返回时把"编辑结果"回传给上一个页面,Router 没有官方回传通道,只能靠全局状态或 EventHub 凑。
- 用户在详情页点系统返回键,希望先弹"未保存,确认退出?",Router 很难拦截。
- 多模块并行开发时,页面路由散落在各处的
pushUrl({ url: 'pages/Detail' })字符串里,重构时一改路径全线崩溃。
这些痛点本质上都是"页面栈不在自己手里"导致的。Navigation 通过 NavPathStack 把栈暴露给业务层,上述问题都有了标准解法。
细节解析
1. Navigation 与 NavDestination 的关系
Navigation 是容器,NavDestination 是栈内每一个页面节点。容器持有一个 NavPathStack 对象,所有压栈/出栈操作都作用在它上面,而不是全局 Router。
// 根页面
Navigation(this.pageStack) {
// 首页内容
}
.title('我的应用')
2. 路由表(route_map)解耦页面路径
在 module.json5 中声明 routerMap,把名字映射到 Builder,业务侧只 push 名字,不写文件路径:
// src/main/resources/base/profile/route_map.json
{
"routerMap": [
{ "name": "Detail", "pageSourceFile": "src/main/ets/pages/Detail.ets", "buildFunction": "DetailBuilder" }
]
}
// module.json5
"routerMap": "$profile:route_map"
3. 参数传递与回传
NavPathStack.pushPathByName(name, param, callback?) 的 param 可以是任意对象;目标页在 NavDestination 的 @Builder 参数里直接拿到。callback 用于接收回传值。
4. 拦截系统返回
NavDestination 提供 onBackPressed 返回值:返回 true 表示"我已消费此次返回,系统不再默认出栈",你可以在里面弹确认框。
示例代码
根页面(持有 NavPathStack)
// src/main/ets/pages/Index.ets
import { DetailParam } from './Detail';
@Entry
@Component
struct Index {
@Provide('pageStack') pageStack: NavPathStack = new NavPathStack()
build() {
Navigation(this.pageStack) {
Column({ space: 16 }) {
Text('首页')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('打开详情(传参并等待回传)')
.onClick(() => {
const param: DetailParam = { id: 1001, from: 'home' }
// 第三个参数为回传回调
this.pageStack.pushPathByName('Detail', param, (result: string) => {
console.info('[Index] 收到详情页回传:', result)
promptAction.showToast({ message: '回传: ' + result })
})
})
Button('直接跳转到详情(不等待回传)')
.onClick(() => {
this.pageStack.pushPathByName('Detail', { id: 2002, from: 'quick' } as DetailParam)
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.title('Navigation 示例')
.mode(NavigationMode.Stack) // 手机用 Stack,折叠屏/平板用 Split 可做侧栏
}
}
详情页(NavDestination + 参数 + 回传 + 拦截返回)
// src/main/ets/pages/Detail.ets
export interface DetailParam {
id: number
from: string
}
// 路由表要求的 Builder,参数通过系统注入
@Builder
export function DetailBuilder() {
// 从 NavPathStack 中取出当前页参数
const stack: NavPathStack = (getContext(this) as any).pageStack
?? AppStorage.get('pageStack') as NavPathStack
Detail(stack)
}
@Component
struct Detail {
// 业务页面不直接声明 NavPathStack,由外部注入
private stack: NavPathStack
@State message: string = '未修改'
// 通过 Navigation 的系统机制拿到参数
@State param: DetailParam = (this.stack.getParamByName('Detail')[0] as DetailParam) ?? { id: 0, from: '' }
aboutToAppear(): void {
// 兜底:从注入的栈读取
const p = this.stack.getParamByName('Detail')[0] as DetailParam
if (p) { this.param = p }
console.info('[Detail] 收到参数:', JSON.stringify(this.param))
}
build() {
NavDestination() {
Column({ space: 16 }) {
Text(`详情页 id=${this.param.id} from=${this.param.from}`)
.fontSize(20)
TextInput({ placeholder: '修改内容', text: this.message })
.onChange(v => this.message = v)
Button('保存并返回(回传结果)')
.onClick(() => {
// 回传并出栈
this.stack.pop(this.message)
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(24)
}
.title('详情')
// 拦截系统返回:弹确认,避免误丢未保存内容
.onBackPressed(() => {
if (this.message.trim() !== '') {
AlertDialog.show({
message: '有未保存内容,确认退出?',
primaryButton: { action: () => this.stack.pop(this.message) }, // 用户确认 -> 回传并退出
secondaryButton: { action: () => {} } // 取消 -> 留在当前页
})
return true // 消费本次返回
}
return false // 无改动 -> 走默认返回
})
}
}
通用:封装一个跳转工具
// src/main/ets/nav/NavUtils.ets
export class NavUtils {
static pushDetail(stack: NavPathStack, id: number, onResult?: (r: string) => void): void {
stack.pushPathByName('Detail', { id, from: 'navutils' }, onResult)
}
static popWithResult(stack: NavPathStack, result: string): void {
stack.pop(result)
}
static replace(stack: NavPathStack, name: string, param?: Object): void {
stack.replacePathByName(name, param)
}
static clearToHome(stack: NavPathStack): void {
stack.clear()
}
}
总结
- 栈要收在自己手里:用
Navigation(this.pageStack)注入NavPathStack,所有跳转都是stack.pushPathByName / pop,告别散落的router.pushUrl字符串。 - 路由表解耦路径:
module.json5+route_map.json把页面名映射到 Builder,重构只改一处。 - 参数类型安全 + 回传:
pushPathByName(name, param, callback)既传参又收回传,比 Router 的全局事件干净得多。 - 拦截返回用
onBackPressed:返回true消费系统返回,可在此做"未保存确认",是 Router 做不到的。 - 多端适配:
NavigationMode.Stack(手机)/Split(平板侧栏)一行切换,无需为不同设备写两套导航。
把页面栈标准化为 Navigation 后,跨模块协作、参数类型、返回拦截都变得可维护,是 NEXT 应用架构的第一步。
更多推荐



所有评论(0)