事件背景

商家工作台目前已经使用qiankun 改造成微前端体系,微前端的好处不用我赘述,大家懂的都懂,因此在有品二清项目开发时也以微前端的方式进行接入。在送测过程中,测试同学反馈在点击浏览器回退的时候会页面空白,我一脸蒙圈的试了好几次却没复现,最终在服务端同学跟测试同学的大力配合下,找到了稳定复现的场景。

问题定位

When you have eliminated the impossibles,whatever remains,however improbable,must be the truth. – 福尔摩斯

为什么会出现这个问题呢?

原因未知,需要继续拆解

以前接入过的子应用有遇到过这种问题呢?

就我所知没有

好的,为什么以前的子应用没有遇到过呢?

因为以前的子应用跟主应用都是vue2的router

那就是说可能是新旧Router导致的咯?

有可能哦~问问其他子应用呢

.....问过了,人家是好的

????.....再报再探!


以上是遇到问题时混乱的大脑的疑点闪现过程。从中我们不难找出导致问题的大致范围:主应用的Router版本与子应用的Router版本不一致,当然,中间还存在几个疑点,需要继续拨开迷雾。

  1. 实现原理有什么差异会导致这个问题。
  2. 其他Vue3项目为啥没遇到这个问题。

带着以上问题,我们开始进入漫长的断点debug 与翻源码过程。

源码分析

真実はいつもひとつ

1. 实现原理的差异

首先,让我们来分析一波新旧版本的 Router 到底更改了什么。
我们在新版本Router文档中找到这么一段说明。
history.state

稍后我们会在源码中找到对应的操作state的具体位置
而旧版本的Router使用的pushState,则只在state中存储了key这唯一一个属性。(vue-router/src/util/push-state.js)
history.state

也就是说,history.state 在新旧版本的Router中的数据结构是不一致的。

我猜这应该是导致改问题的核心因素了。我们接着往下验证

2. undefined 的由来

  1. 主应用在点击左侧菜单的时候,会触发pushState,从而激活子应用,进入子应用对应的菜单中。
    history.state

  2. 子应用在第一次进入的时候,会执行changeLocationreplace操作,此时页面进入子应用内。

  3. 子应用内部页面跳转时,所有的路由均由子应用Router进行接管。此时一切正常。

  4. 再次点击主应用左侧菜单时,执行以下代码将 history.state 置为空对象。同时,页面跳转到对应路由页面。

    history.pushState({}, '', jumpUrl)
  5. 继续在子应用内部跳转路由时,如下图所示,当执行到 push(vue-router/history/html5.ts) 的时候,由于此时 history.state 已经变成了空对象,所以图中的 currentState.current 就变成了undefined

    在265行会先调用一次changeLocation用来记录当前路由信息(中间状态)

history.state

  1. 而进入到 changeLocation 方法内部,此时的 to 已经变成了undefined,也因此拼装的 url 中会出现undefined
    history.state
  2. 由于第六步中执行的是中间态的记录当前路由行为,后续还会继续跳转真实的目的地路由,所以中间态的undefined被保存在了浏览器的路由栈中。当触发回退的时候,就跳到了url中带有undefinedresole的空白页面。
  3. 现在,案情真相大白了。

解决方案

history.state

一劳永逸的方案则是:主应用与子应用均使用同一个版本的 VueRouter,当然,也要从实际出发,综合考虑各项改动的ROI,从而采用更合理的方案。

One More Thing!

为什么其他使用新Router的子应用没有遇到同样的问题?
我盲猜是因为 其他子应用要么只有一个主应用的菜单,要么子应用之间的菜单比较独立,所以没有遇到。实际上,这应该是个必现的场景。