声明
-
本文讨论的
history.pushState所处场景,仅限于使用阿里 qiankun 微应用框架改造后的主应用环境- 因为
vue-router自身并不监听window.popstate事件(至少vue-router不会对用户手动调用的history.pushState做出响应) - 经过
阿里 qiankun改造后的主应用,会监听 window.popstate,根据变化的 history 记录,动态改变主应用的vue-router记录,并将路由变化分发给各个微应用。
- 因为
-
文中有时会使用
主应用和各微应用的代指名称- 主应用 :
appMain - 微应用 :
app1、app2.... - 其他微应用 :
appX
- 主应用 :
bug复现场景
app1服务开启,app3服务未开启,appMain服务开启,加载app1成功,加载app3失败
Uncaught TypeError: application 'app3' died in status LOADING_SOURCE_CODE: Failed to fetch
// or
Uncaught (in promise) TypeError: Failed to fetch
操作步骤
-
步骤1:在
app1中点击按钮前往app3。(通过window.history.pushState({},"","/app3")) 因为app3服务未开启,appMain.$router监听到 history栈变化(由乾坤生成的监听,vue本身貌似没有监听这个变化),加载app3失败且同时app1.mount(),乾坤容器呈空白。 -
步骤2:然后调用
appMain.$router.push()添加新的路由,页面没有反应,浏览器history记录也没有变化,控制台会出现关于vueRouter记录underfined问题 。
vue-router.esm-bundler.js?6c02:72
[Vue Router warn]: Error with push/replace State DOMException: Failed to execute 'replaceState' on 'History': A history state object with URL 'http://192.168.1.111:8080undefined/' cannot be created in a document with origin 'http://192.168.1.111:8080' and URL 'http://192.168.1.111:8080/childApp/app3'.
at History.eval [as replaceState] (webpack-internal:///./node_modules/single-spa/lib/esm/single-spa.min.js:33:10677)
at changeLocation (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:566:60)
at Object.push (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:601:9)
at finalizeNavigation (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:3202:31)
at eval (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm-bundler.js:3078:27)
该问题,在appMain或其他微应用中,通过history.pushState添加新的不存在的路由记录后,再调用this.$router.push() 都会导致该问题出现。
而使用appMain.$router.push添加任何不存在的路由记录,都不会出现该问题。
尝试过的操作的:
history.pushState()添加的未知路由后,主应用不使用router.push("/404"),而是改用router.replace("/404"),则不会出现。微应用使用history.pushState(state)时,state设置为null,则不会出现该问题。
问题思考
可参考线索
- 通过
appMain.$router.push()添加的未知路由则不会导致该bug - 通过
history.pushState()添加的未知路由会导致该bug history.pushState()是否会触发vueRouter的导航守卫 乾坤项目:会 非乾坤项目:不会
现在,我们手头仅有的线索都指向history.pushState(),我们看看vue-router 4.x文档有关 history.pushState用法 的解释:
我们发现,vue-router官方推荐避免使用pushState,推荐使用replaceState,且重点标注了需要保留history.state状态。
vue-router保留的当前状态究竟是什么呢?
我们分别将$router.push()、app1.history.pushState前后的history.state打印出来,看看他们的区别。
$router.push()前后的history.state:
router.beforeEach((to, from, next) => {
console.log("history.state before $router.push()",JSON.parse(JSON.stringify(history.state)));
next();
});
router.afterEach((to, from) => {
console.log("history.state after $router.push()",JSON.parse(JSON.stringify(history.state)));
});
可以看到,通过vue-router自身的push方法,history.state中,始终都记录了同样的状态字段。 我们可以在vue-router.js找到匹配的结构
history.pushState()前后的history.state:
console.log("history.state before",JSON.parse(JSON.stringify(history.state)));
window.history.pushState(
{origin: 'son-app1',id: 'test-000001',orderId: '100000011',},
'',
'/childApp/app3'
);
console.log("history.state after",JSON.parse(JSON.stringify(history.state)));
history.pushState(null,"","/path")后的history.state:
所以,大胆猜测一下:vur-touer的路由模拟,需要history.state中记录相关的状态,而我们手动history.pushState()时,并没有记录或保存vue-router需要的state有关状态,才导致出现的该问题呢?
验证猜想
我们先将app1.history.pushState(state)的state,补全至符合vue-router格式试试。(快捷省事,直接复用:实际项目需要另行记录back、current、scoll...等状态值)
window.history.pushState(
history.state,
'',
'/childApp/app1/myFee'
);
试一下
OK,到这里,history.pushState会导致vue-router undeifned 问题就解决了。