记一次动态注册路由导致的路由跳转阻塞问题
背景
最近在写的项目遇到这样一个问题:
页面在登录后,查询一个接口获取租户 ID,然后异步刷新页面,把获取到的租户 ID 挂载在路由路径里
在获取到租户 ID 后的这次刷新页面的路由守卫(Router Guard)的钩子里,代码会动态下载一段脚本并执行,该脚本会调用 Vue Router 的 addRoute() 方法动态注册路由
之后执行路由守卫的 next() 放行本次跳转
问题
问题来了,由于该次刷新页面是异步操作,而且动态下载脚本也是异步操作,有一定的几率,addRoute() 和 next()在同一个 Vue 的刷新任务队列里执行,然后就会发生路由跳转停止,反映到页面上,就是
页面点击登录后不再跳转到主页面
解析
之前有同事跟我说,这是因为 addRoute() 会打断路由跳转,所以我们写了一些代码来规避这件事
总的思路是,在这次异步 next() 之前发送事件通知脚本延后执行 addRoute(),这样做有一个问题,即延后多久执行 addRoute(),我们原先设定的是200ms,但结果是仍然有概率与 next() 运行在同一个任务队列里,再延长这个时间其实也不是最好的办法,故这个问题搁置了很久
直到有客户的自动化测试因为这个问题而打断运行,我终于不得不仔细研究这个问题
查看 Vue Router 3 的源码,我发现在 addRoute() 的定义如下:
1 | addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) { |
其中的 this.matcher.addRoute(parentOrRoute, route) 这一段只是在对路由信息进行维护,并不会有打断路由跳转的风险
问题出在下面的 if 判断中,对于我们的登录后异步加载脚本并执行路由注册的代码逻辑,this.history.current 一定不会与 START 相等(因为 this.history.current 是登录页,而 START 是根路由),那么根据这段代码,Vue Router 会尝试路由到当前页面,即登录页
在 history.transitionTo() 方法里,根据传来的路由,设置目标跳转对象 pending,简化后的源码如下
1 | transitionTo ( |
由上面的代码可知,此时的 pending 已经由根路由改为了登录页路由
好了,当异步的 next() 在这个任务队列里被调用,就会发现,目标跳转路由是登录页,当前路由也是登录页,路由跳转停止
解决
了解了打断路由跳转的原因,兼容代码就好写了,我翻看了 github 的 issues,发现有人提出,这里其实不做那次 history.transitionTo() 也是可以正常注册成功的,于是我调整了注册路由的代码,不再调用 router.addRoute(),而是调用 router.matcher.addRoute() 绕过了多余的 history.transitionTo()
1 | const addRoutes = function(routes) { |
深入
那么,代价呢?
其实我还没看明白 Vue Router 在这里刻意这么做的目的是什么,但是由此确实带来一个问题,即当登录后跳转的页面是新注册的路由时,由于该路由在跳转时正在注册,于是 next() 时便找不到该路由信息,于是页面会停止渲染,变成空白页面
我现在的解决方案是,当识别到 pending 是准备注册的路由时,在执行 addRoute() 的200ms后,使用 router.replace() 刷新当前页面,从而使得已注册的路由被正确地渲染


Mosu is located on the shore of Mosu Lake, facing the vast Chu Sea, backed by the Yihan Mountains. Thousands of miles of Mosu Desert can not erode the Mosu Valley. Thus the Mosu Empire was established.


