众所周知,setTimeout 一般用于延时处理,但当用户的网速比 setTimeout 设定的延时更慢时,就会引发一系列不可预知的 bug……
举个例子,当前的页面路由栈是 A -> B,在 B 页面进行一系列操作后要返回 A 页面,A页面的 UI 要根据 B 页面的操作结果来展示;比如 B 页面有两个按钮,点击它们都会返回 A 页面,但点第一个回到 A 会弹出一个 Dialog 弹框,点第二个按钮则不会。
拿到这个需求该如何处理呢?
一、使用 EventBus
首先想到的是使用 EventBus,点第一个按钮的时候,在 router.back()
之后发送一个事件通知 A,然后在 A 页面监听这个事件,监听到事件发出之后弹出 Dialog。
简便起见我们用打印 log 的方式来看效果:
代码语言:javascript复制// B页面
function goBack1() {
router.back()
console.log("page B back");
$bus.emit("showDialog", {});
}
// A 页面
$bus.on("showDialog", () => {
console.log("收到 showDialog 事件");
});
onMounted(() => {
console.log("page A onMounted");
});
这样看似没问题,但忽略了一点,router.back()
其实是异步的,所以 page B back
会比 page A onMounted
先打印出来,监听 showDialog 事件的 log 也没打印出来:
page B back
page A onMounted
因为在页面 A 监听事件之前,事件已经被发出了,没机会消费。
这时聪明的你会想到,如果router.back()
是异步的,那在它前面加个 await
不就行了。我们来看一下 back 的定义:
back()
方法并没有像 push
和 replace
那样返回 Promise,所以即使在前面加 await
也没有用,跟上面的结果一样。
二、EventBus setTimeout
既然在back()
之后马上发送 eventbus 不行,聪明的你又想到,给 eventbus 加个延时不就解决了!
function goBack1() {
router.back();
console.log("page B back");
setTimeout(() => {
$bus.emit("showDialog", {});
}, 200)
}
延时 200 毫秒,在 200 毫秒内页面肯定加载完了吧,看看 log
代码语言:javascript复制page B back
page A onMounted
收到 showDialog 事件
果然,顺利的完成了这个需求,开开心心下班。
过了两天,测试同事跟你提了个 bug,说从 B 页面返回 A 之后,没有弹出弹框。你不信,亲自在页面上试了试,没问题呀!
跟测试一顿 battle 后,发现他那里的确没弹出弹框。你的小脑袋瓜子飞速运转,问题到底出在那里?一样的代码,为什么不同人运行的效果不一致,难道是人品问题……
突然灵机一动,唯一的变量是网速!
假如 A 页面逻辑很复杂,要加载很多资源,一般网速快的话,200 毫米内是肯定可以初始化完成的,但是如果用户网速特别慢, slow 3G 时代,200毫秒页面不一定能初始化完成,也就会出现发送 eventbus 在监听之前,导致没有监听到的结果。
那增加延迟时间呢?其实不是时间问题,因为不知道用户的网络到底有多慢,即使设 5秒也不一定绝对安全,且太长了会影响用户体验。所以这种方法不可取,不确定性因素太多。
三、最优解
有人说可以用 vuex
,从 B 点第一个按钮返回时,在vuex
中记录一个变量,A页面读取这个变量判断该展示什么逻辑。这种方式其实也不保险,变量什么时候重置呢?总会有各种极端情况绕过你设置的重置条件。维护中间状态,易出错且成本高。
那将 A 页面做路由缓存呢?首先业务场景要求不能缓存,其次并不能解决不同方式返回处理不同逻辑的需求。
最稳妥的方法是不要用 back()
,用 replace()
并且在 url 上带上参数,A 页面读取 url 上的参数根据不同状态做出不同动作,一个状态对应确定的一个动作,不管网速如何变化,url 是确定的,就能得到确定的结果。