1. 问题场景:为什么H5页面一刷新,返回键就“失灵”了?
最近在做一个H5项目,用的是uniapp框架,本来一切都挺顺的。直到接入了某个第三方服务,比如人脸核身或者支付回调,问题就来了。用户完成操作后,第三方页面会回调到我指定的H5页面,这时候用户想点左上角的返回按钮,或者我们调用 uni.navigateBack() 想让他回到上一个页面,结果发现页面没反应,甚至有时候页面还会在原地“鬼畜”般地闪烁一下,就是回不去。
我当时第一反应是:代码写错了?检查了好几遍,uni.navigateBack() 调用没问题啊。然后祭出调试大法,在回调页面里打印了一下 getCurrentPages()。这一打印,真相大白了:页面栈里,就只剩下当前这一个页面了。
什么叫“页面栈”?你可以把它想象成浏览器里你点过的那些网页,它们会形成一个历史记录列表,你按返回键,浏览器就从这个列表里退回到上一个记录。在uniapp里,它自己维护了一个类似的栈结构,用来管理我们通过 uni.navigateTo 跳转的页面顺序。uni.navigateBack() 这个API,就是依赖这个栈来工作的,它告诉应用:“从栈里弹出最上面那个页面,让我回到下面那个。”
但是,H5环境下的页面刷新(无论是第三方回调触发的,还是用户手动按了F5),是一个“破坏性”操作。它相当于把浏览器当前标签页的“历史”给重置了,同时,也把uniapp内部辛辛苦苦维护的那个页面栈给清空了。这时候,栈里就只剩刚刷新加载的这个页面孤零零的一个。你再调用 uni.navigateBack(),它一看栈里就一个页面,没地方可退,要么就报错,要么就啥也不干,这就是返回失效的根本原因。
这不仅仅是uniapp的问题,几乎所有在H5环境下使用类似路由栈管理方案的框架(比如一些小程序转H5的方案),都可能踩到这个坑。用户感知就是产品有bug,体验非常糟糕。所以,我们必须设计一个方案,在uniapp的页面栈“失灵”的时候,能有备用的方法把用户送回去。
2. 核心思路:双保险机制,让返回“稳如老狗”
既然知道了问题出在页面栈被清空,那解决方案的核心思想就很明确了:给返回操作上“双保险”。不能只依赖 uni.navigateBack() 这一条路,得给它找个备份。
我的设计思路是这样的:每次尝试返回时,我们先做个检查。检查什么呢?就是去查一下当前的页面栈深度,也就是调用 getCurrentPages().length。如果栈的长度大于1,说明uniapp自己的路由历史是正常的,有上一页可以回。这时候,我们毫不犹豫地使用原生的 uni.navigateBack(),这是最标准、体验最好的方式。
关键在于,如果检查发现栈的长度等于1,怎么办?这就意味着我们正处在我刚才说的那个“危险”场景里:页面栈被清空了,只剩下当前页。这时候,uni.navigateBack() 已经靠不住了。我们的备用方案就要启动,这个备用方案就是浏览器的历史记录对象——window.history。
虽然uniapp的页面栈被刷新搞乱了,但浏览器的会话历史(session history)在大多数情况下,仍然保留着用户跳转到当前页之前的那个记录。我们可以利用 history.back() 或者 history.go(-1) 来模拟返回操作,让浏览器导航到上一个历史记录。这样,用户就能顺利返回了。
这个方案的精髓在于“兼容”和“无缝”。对于用户和大部分开发者来说,他们不需要知道底层用了哪种返回方式,他们只需要调用一个统一的、可靠的返回方法。我们的封装,就是要屏蔽掉这些底层差异,让返回行为在任何情况下都 predictable(可预测)。
3. 方案实现:手把手封装一个“聪明”的返回函数
理论说清楚了,咱们直接上代码,看看这个双保险的返回函数具体怎么实现。我会把代码拆开,一点一点讲清楚。
首先,我


281

被折叠的 条评论
为什么被折叠?



