vue-router重复点击报错和新链接跳转失败
2021-01-14
最近解决了项目的前端路由问题,具体而言有两个:
- 路由重复点击,会在控制台输出报错
- 一个 vue-router 渲染出来的 a 标签,右键新链接打开,在新页面无法打开网页
由于我们公司内外网隔离,所以无法具体截图现象。
路由重复点击,会在控制台输出报错
报错的大概内容为: vueAll.js?v=3.0:2 Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxxx
。
阅读 vue-router 的文档,可以看到:
router.push 或 router.replace 将返回一个 Promise
查看 vue-router 源码,从源码此处可以看出,我们对此方法进行异常处理,便可一劳永逸解决此问题。
因此在初始化路由时,对此方法进行处理:
import VueRouter from 'vue-router';
function handleRouterErr (methodsList) {
methodsList.forEach(item => {
const ORIGIN_METHOD = VueRouter.prototype[item];
VueRouter.prototype[item] = function (location) {
let vm = this;
return ORIGIN_METHOD.call(vm, location).catch(err => err);
}
});
}
// 调用
handleRouterErr(['push', 'replace'])
无法在新标签页打开链接
右键一个 vue-router
渲染的 a
标签,新标签页打开后页面显示 Not Found
。
base配置不正确
首先,我发现我们所有的页面都是 ip/index#xxxx
的形式,但是此时打开的链接确是 ui/#/#/xxxx
的形式。也就是说渲染出来的a标签的href是不正确的。因此很容易想到我们路由配置有问题。
路由是这样写的:
router = new VueRouter({
routes: ROUTER_DATA,
base: '/ui/#'
});
那显然这个 base 是不对的,期望最后渲染的href应当是 index#xxx
的形式,所以这里应该将base改成 /index/
。
为什么这里不需要 # 符号了?因为查看 vue-router
代码可以看到,构造 href 逻辑如下:
function createHref (base: string, fullPath: string, mode) {
var path = mode === 'hash' ? '#' + fullPath : fullPath
return base ? cleanPath(base + '/' + path) : path
}
mode 默认为 hash,所以会自动添加 # 号,这也解释了为何新标签页的那个链接存在两个 #。
我们的服务器上并没有 ui 这个目录,所以自然返回 404。
新标签页打开报错
当修改完base后,这次新标签页打开的链接似乎正常了:ip/index/#/xxxx
,但是打开页面白屏,控制台报错了:uncaught syntaxerror unexpected token '<'
。
仔细一看,发现此时链接其实还是不正确的,相比于正确的链接,在index后面多了一个 /。为何多了这个会导致报错?
我选择一个报错的js文件请求,从请求中可以看到两个问题:
- 请求的js文件链接不正确。正常情况下,这个js的请求应该是:
/static/xxx.js
,但是此时却是/index/static/xxx.js
Content-Type
不正确,此时是text/html
,返回的也是一个html文件
寻其根本,还是第一个请求不正确的问题导致的。
这些报错文件我看了一下,是在 webpack 打包后的 index.html 中引入的,在这个 html 文件中,都是以相对路径 ./static/xxx
引入的。于是我想这里应该改成绝对路径,于是我将webpack配置中的 relativePublicPath
设置为 true
,重新生成打包文件后,问题就解决了。
更简单的解决方式
我上面的解决方式改了三个地方:
- 修改 base
- 修改入口文件资源路径
- 修改 webpack relativePublicPath 配置
组长觉得应该不需要这样改,他搜索了 base 相关信息,发现大家都不配置 base,于是和我说删掉 base 这个配置试试。我一试,还真就解决新标签页跳转问题了。
那么为什么这种修改方式是可以的?
首先还是回到链接身上观察一下,在不修改 base 的情况下:
- 正确的链接是
ip/index#xxxx
- 有问题的链接是
ip/ui/#/#/xxxx
然后再来看 vue-router 构造 href 的规则:
function createHref (base: string, fullPath: string, mode) {
var path = mode === 'hash' ? '#' + fullPath : fullPath
return base ? cleanPath(base + '/' + path) : path
}
我们项目的 base 配置是 /ui/#
,mode 默认为 hash ,fullPath 是 router-link 中设置的 to 参数,那么此时 path 就是 #/{fullPath}
,我们配置了 base,那么最终结果就是 /ui/#/#/{fullPath}
。路由链接发生了改变,浏览器会重新请求资源,但是服务器上并没有 /ui/ 这个文件导致无法返回正确的页面。
此时我们把base删去,那么整个 createHref 的结果就是 #/{fullPath}
。我们改变的仅仅只是 hash 部分,hash 部分并不会被包括在 http 请求中,它是用来指导浏览器动作的,对服务器端没影响,因此,改变 hash 不会重新加载页面,也就是此时此链接点击时,浏览器跳转的实际地址为 ip/index#{fullPath}
,和我们希望的链接形式是一致的。
删除一行代码就解决了。
那么这种改法是偶然还是通用?
之所以说是不是偶然,因为整个过程看起来似乎是刚好拼成了我们期望的链接形式。对于这个问题,我们需要看看 base 到底是什么。
文档上写的是:
应用的基路径。例如,如果整个单页应用服务在 /app/ 下,然后 base 就应该设为 “/app/”
举个例子:
假设单页应用的入口文件地址是:www.xxx.com/test/index.html
,那么 base 应当是 /test/index.html
。
如果后端服务在 /test/ 下,那么后端配置 Nginx 配置,将 /test/ 重定向到 /test/index.html,那么我们的 base 此时就是写 /test/。
实际项目:
在我们的项目中,我们访问项目时,整个过程是 ip -> ip/index
,从请求中可以看到发送了一个 ip/index
的网络请求,这个请求指向一个 cgi 文件,最终返回了 index.html 文件,那么此时前端代码或者说得更小一点就是 vue-router 开始工作了,渲染出正确的组件。
那么回到最开始的问题:这种改法是偶然还是通用?
答案是通用的。因为我们配置了项目的根路径重定向到 /index,所以这里我们的 base 应该是配置 / ,但是vue-router默认 base 为 /,所以我们可以删去。这么一品,反而也有一些偶然的味道了。