4、页面路由设计:Router模块使用、页面栈管理、参数传递与返回
页面路由,说白了就是决定「用户点了个按钮,接下来该跳转到哪个页面」。在鸿蒙开发里,Router模块就是干这个活的。我刚开始接触时觉得这不就是个跳转嘛,但真正做多媒体播放器这种多页面应用时,才发现路由设计的好坏,直接影响用户体验和代码维护成本。
4.1 Router模块基础用法
鸿蒙的Router模块,提供了页面跳转的核心能力。它支持两种跳转方式:push(压栈)和replace(替换)。
先看一个最简单的例子:
// 从首页跳转到播放页面
import router from '@ohos.router';
@Entry
@Component
struct HomePage {
build() {
Column() {
Button('播放音乐')
.onClick(() => {
router.pushUrl({
url: 'pages/PlayerPage'
});
})
}
}
}
嗯,这里要注意:url 路径是相对于 pages 目录的。我在项目中遇到过有人把路径写成了绝对路径,结果编译报错,找了半天才发现是路径格式问题。
4.2 页面栈管理:push与replace的区别
页面栈,你可以想象成一个「历史记录堆叠」。每次push,就像往栈顶放一张卡片;每次返回,就像从栈顶取走一张卡片。
| 方法 | 行为 | 页面栈变化 | 适用场景 |
|---|---|---|---|
| pushUrl | 压入新页面 | 栈长度+1 | 正常跳转,保留返回路径 |
| replaceUrl | 替换当前页面 | 栈长度不变 | 登录后跳转主页,不希望返回登录页 |
| back | 返回上一页 | 栈长度-1 | 用户点击返回按钮 |
| clear | 清空页面栈 | 栈长度=1 | 退出登录,回到首页 |
为什么要有replace?我举个例子。在播放器里,用户从「歌曲列表」点进「播放页面」,这时候如果用push,用户按返回键会回到列表页,这没问题。但如果是「登录页」跳「主页」,你肯定不希望用户按返回还能回到登录页吧?这时候replace就派上用场了。
4.3 参数传递:让页面之间「对话」
页面跳转时,经常需要带一些数据过去。比如从歌曲列表点进播放页,你得告诉播放页「要播哪首歌」。Router模块支持两种参数传递方式:params 和 data。
4.3.1 使用params传递参数
// 发送方:歌曲列表页
router.pushUrl({
url: 'pages/PlayerPage',
params: {
songId: '12345',
songName: '晴天',
artist: '周杰伦'
}
});
// 接收方:播放页面
@Entry
@Component
struct PlayerPage {
@State songId: string = '';
@State songName: string = '';
@State artist: string = '';
aboutToAppear() {
// 从路由参数中获取数据
const params = router.getParams();
if (params) {
this.songId = params['songId'];
this.songName = params['songName'];
this.artist = params['artist'];
}
}
}
我个人习惯在 aboutToAppear 生命周期里获取参数,因为这时候页面还没渲染,数据准备好了再展示,不会出现「先空白再显示」的闪烁问题。
4.3.2 参数传递的注意事项
- 参数类型: params支持基本类型和JSON对象,但不支持函数或复杂对象。我踩过坑,传了一个Date对象过去,结果变成了字符串。
- 参数大小: 别传太大的数据,比如整个歌曲列表。建议只传ID,接收方再通过ID去获取详情。
- 参数安全: 敏感信息(如token)不要通过路由参数传递,容易被拦截或泄露。
4.4 页面返回与数据回传
有时候,从B页面返回A页面时,需要把B页面的结果带回去。比如在播放器里,用户从「设置页」调整了播放模式,返回时需要通知「播放页」更新状态。
// B页面:设置页,返回时带数据
router.back({
url: 'pages/PlayerPage',
params: {
playMode: 'shuffle' // 改为随机播放
}
});
// A页面:播放页,监听返回
aboutToAppear() {
const params = router.getParams();
if (params && params['playMode']) {
this.playMode = params['playMode'];
// 更新播放模式
this.updatePlayMode();
}
}
这里有个细节:router.back() 的 url 参数是可选的。如果不传,默认返回上一页。但如果你传了,它会检查当前页面栈中是否有这个页面,有的话就返回到那个页面,没有的话就报错。
4.5 页面栈深度管理
在实际项目中,页面栈不能无限增长。用户点来点去,栈里堆了几十个页面,内存迟早撑不住。我建议做以下管理:
- 限制栈深度: 比如最多保留10个页面,超过时自动清理栈底的页面。
- 使用replace代替push: 对于「详情页→详情页」这种连续跳转,用replace替换当前页,避免栈无限增长。
- 监听页面栈变化: 通过
router.getState()获取当前栈信息,做动态调整。
// 获取当前页面栈信息
const state = router.getState();
console.info('当前页面栈深度:', state.index + 1);
console.info('当前页面路径:', state.path);
// 如果栈深度超过10,清理栈底
if (state.index >= 10) {
router.clear();
router.pushUrl({
url: 'pages/HomePage'
});
}
router.clear() 一键清空,用户反馈体验好了很多。
4.6 路由拦截与权限控制
有些页面需要登录才能访问,比如「个人中心」。这时候可以在路由跳转前做拦截:
function navigateWithAuth(url: string, params?: Object) {
const isLoggedIn = AppStorage.get<boolean>('isLoggedIn');
if (!isLoggedIn) {
// 未登录,跳转到登录页
router.pushUrl({
url: 'pages/LoginPage',
params: {
redirectUrl: url, // 登录成功后跳回原页面
redirectParams: params
}
});
return;
}
// 已登录,正常跳转
router.pushUrl({
url: url,
params: params
});
}
嗯,这里有个小坑:AppStorage 是全局存储,但页面销毁时数据可能会丢失。我建议把登录状态同时存在本地存储里,双重保险。
4.7 总结一下
页面路由设计,说白了就是三件事:怎么跳、怎么传数据、怎么回来。记住几个要点:
- push用于正常跳转,replace用于「不回头」的场景
- 参数只传ID,不传大数据
- 页面栈要控制深度,别让用户「回不去」也别让栈「撑爆」
- 敏感页面加路由拦截,别让未登录用户乱闯
你想想看,如果路由设计得好,用户操作起来行云流水,代码维护也轻松。如果设计得乱,用户点几下就卡死,你还得满世界找bug。所以,花点时间把路由规划好,绝对是值得的。