HarmonyOS7 Navigation 不只是跳页:路由拦截、深链接和返回值一次讲透 文章目录前言Navigation 和 router 到底啥区别NavPathStack 基础操作路由拦截器登录态和权限校验深链接配置页面间返回值小结前言做过鸿蒙项目的人都知道早期版本路由跳转全靠router模块。用起来还行但一到复杂场景就头疼——页面栈不透明、传参靠 global、返回值得自己封装回调。HarmonyOS 7 的Navigation组件彻底改变了这个局面它把路由栈管理、拦截器、深链接全整合到了一起。这篇就聊聊我在实际项目中怎么把 Navigation 跑通的尤其是拦截器和返回值这两块踩了不少坑。Navigation 和 router 到底啥区别简单说router是函数式调用直接操作一个全局栈Navigation是声明式组件路由栈由你手里持有的NavPathStack对象控制。最大的区别在于Navigation 的路由栈是可见、可控的。你可以随时查看栈里有几个页面、删掉中间的页面、替换当前页面甚至拦截某次跳转。router做不到这些或者说做起来很别扭。新项目建议直接上 Navigation别犹豫。NavPathStack 基础操作先看最基本的栈操作。我习惯把 NavPathStack 封装成一个单例全局复用// router/RouterManager.etsexportclassRouterManager{privatestaticinstance:RouterManagerprivatepathStack:NavPathStacknewNavPathStack()staticgetInstance():RouterManager{if(!RouterManager.instance){RouterManager.instancenewRouterManager()}returnRouterManager.instance}getPathStack():NavPathStack{returnthis.pathStack}push(name:string,param?:object){this.pathStack.pushPath({name:name,param:param})}pop(){this.pathStack.pop()}// 返回到指定页面中间的页面全部出栈popToName(name:string){this.pathStack.popToName(name)}// 清空栈只保留根页面clear(){this.pathStack.clear()}// 替换当前页面replace(name:string,param?:object){this.pathStack.replacePath({name:name,param:param})}// 删除栈中指定名称的所有页面removeByName(name:string){this.pathStack.removeByName(name)}}在根页面里把 pathStack 绑到 Navigation 上EntryComponentstruct MainPage{privaterouterRouterManager.getInstance()build(){Navigation(this.router.getPathStack()){// 首页内容Column(){Button(去商品详情).onClick((){this.router.push(ProductDetail,{id:10086})})}}.navDestination(this.navDestinationBuilder)}BuildernavDestinationBuilder(name:string,param:object){if(nameProductDetail){ProductDetailPage(paramasProductParam)}elseif(nameOrderConfirm){OrderConfirmPage(paramasOrderParam)}}}这里有个坑navDestinationBuilder里拿到的param类型是object你需要自己做类型断言。我一般会在每个页面定义一个明确的接口类型跳转时保证数据结构一致。路由拦截器登录态和权限校验这个功能太实用了。想象一下用户没登录就点我的订单你得拦截住这次跳转先跳到登录页登录成功后再跳回来。Navigation 原生支持拦截器通过setInterception搞定// 在 App 初始化时设置拦截器RouterManager.getInstance().getPathStack().setInterception({// 跳转前触发willShow:(from:NavDestinationInfo,to:NavDestinationInfo){constneedLogin[OrderConfirm,UserProfile,Payment]if(needLogin.includes(to.name)!UserManager.isLoggedIn()){// 保存目标页面信息登录后跳回来LoginState.pendingRoute{name:to.name,param:to.param}// 返回 false 阻止本次跳转returnfalse}returntrue},// 跳转完成后触发适合做埋点didShow:(from:NavDestinationInfo,to:NavDestinationInfo){Analytics.trackPageView(to.name)}})登录成功后检查有没有待跳转的页面// 登录成功回调里onLoginSuccess(){if(LoginState.pendingRoute){constpendingLoginState.pendingRoute LoginState.pendingRouteundefinedRouterManager.getInstance().push(pending.name,pending.param)}}willShow返回false就能拦住跳转非常干净。我之前用 router 模块时得在每个页面的aboutToAppear里手动检查登录态代码散了一地。深链接配置深链接就是从外部浏览器、短信、推送通知直接跳到 App 内指定页面。HarmonyOS 7 的配置分两步。先在module.json5里声明 scheme{module:{skills:[{entities:[entity.system.browsable],actions:[action.system.want],uris:[{scheme:myapp,host:product,path:/detail},{scheme:myapp,host:order,path:/confirm}]}]}}然后在 UIAbility 的onCreate或onNewWant里解析 URIonCreate(want:Want){this.handleDeepLink(want)}onNewWant(want:Want){// App 已启动时收到深链接this.handleDeepLink(want)}privatehandleDeepLink(want:Want){consturiwant.uriif(!uri)returnif(uri.startsWith(myapp://product/detail)){// 解析参数比如 myapp://product/detail?id123constparamsnewURLSearchParams(uri.split(?)[1])constproductIdparams.get(id)RouterManager.getInstance().push(ProductDetail,{id:productId})}}注意onNewWant这个生命周期——App 在前台运行时收到深链接会走这里不走onCreate。我一开始只写了onCreate死活触发不了排查了半天才发现这个问题。页面间返回值这个需求很常见从商品列表跳到详情页详情页点了立即购买后回到列表页列表页需要刷新购物车角标。在 Android 上有startActivityForResultNavigation 里怎么做NavPathStack 的pushPath支持传一个animated参数和一个回调但更优雅的方式是利用pop时传值// 商品详情页Componentstruct ProductDetailPage{StateproductId:stringbuild(){Column(){Button(立即购买).onClick((){// 通过 NavPathStack 的 setResult 设置返回值constpathStackRouterManager.getInstance().getPathStack()// 获取栈顶的 NavDestination 并设置结果pathStack.setPreviousResult({action:buy,productId:this.productId})pathStack.pop()})}}}列表页接收返回值我推荐用NavPathStack的interception配合自定义事件// 商品列表页Componentstruct ProductListPage{build(){Column(){Button(去详情页).onClick((){constpathStackRouterManager.getInstance().getPathStack()pathStack.pushPath({name:ProductDetail,param:{id:123}})// 监听 pop 回来// 方案在 willPop 拦截器里获取 result})}}}实际项目中我更推荐一个简单粗暴的方案——用AppStorage做事件总线// 详情页 pop 之前AppStorage.setOrCreate(refreshCart,Date.now())pathStack.pop()// 列表页监听StorageProp(refreshCart)refreshFlag:number0// refreshFlag 变化时触发刷新虽然不是最优雅的方案但胜在简单可靠跑通率 100%。小结Navigation 的路由栈管理比老 router 强太多了。拦截器让你不用再到处写权限检查代码深链接让外部跳转有了统一入口返回值虽然目前 API 不算特别顺手但有多种替代方案可以用。我的建议是新项目一开始就把 RouterManager 封装好把拦截器、深链接解析、页面路由表全配上。后面开发新页面只需要往路由表里加一行省心得很。路由这块基础设施搞好了后面写业务逻辑会顺畅很多。