一、概念阐述
对于 Vue 这类渐进式前端开发框架,为了构建 SPA(单页面应用),需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。
为了达到这一目的,浏览器当前提供了以下两种支持:
-
hash —— 即地址栏 URL 中的
#
符号(此 hash 不是密码学里的散列运算)。
比如这个 URL:[http://www.abc.com/#/hello](http://www.abc.com/#/hello)
,hash 的值为#/hello
。它的特点在于:hash 虽然出现在 URL 中,
但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。 -
history —— 利用了 HTML5 History Interface 中新增的
pushState()
和replaceState()
方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的back
、forward
、go
的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
因此,hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。
二、为什么变迁
一般场景下,hash 和 history 都可以实现路由跳转,默认是hash模式。那么为什么要变迁呢?理由如下:
1、颜值
“#”符号夹杂在 URL 里看起来确实有些不太美丽,所以要把#去掉。
2、跨域问题
在项目开发过程中,路由跳转带#的url请求和普通接口url请求的混用,产生了令人头疼的跨域问题。需要将url中的“#”去掉。
3、其他
根据 Mozilla Develop Network 的介绍,调用 history.pushState() 相比于直接修改 hash
,存在以下优势:
-
pushState()
设置的新 URL 可以是与当前 URL 同源的任意 URL;而hash
只可修改#
后面的部分,因此只能设置与当前 URL 同文档的 URL; -
pushState()
设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而hash
设置的新值必须与原来不一样才会触发动作将记录添加到栈中; -
pushState()
通过stateObject
参数可以添加任意类型的数据到记录中;而hash
只可添加短字符串; -
pushState()
可额外设置title
属性供后续使用。
当然,history
也不是样样都好。SPA 虽然在浏览器里游刃有余,但真要通过 URL 向后端发起 HTTP 请求时,两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。
-
hash
模式下,仅hash
符号之前的内容会被包含在请求中,如[http://www.abc.com](http://www.abc.com/)
,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。 -
history
模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如[http://www.abc.com/book/id](http://www.abc.com/book/id)
。如果后端缺少对/book/id
的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
三、如何变迁
1、前端修改
在创建VueRouter对象的地方添加mode:'history'属性。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
注意,如果前端文件不是部署在服务器的根目录下,还要修改另外几个地方。
假如部署在Project文件夹下,在new VueRouter对象的时候,要添加一个base属性,如下所示:
const router = new VueRouter({
mode: 'history',
base: '/Project/',
routes: [...]
})
另外,在config-->index.js文件中,将assetsPublicPath属性也改为/Project/,如下所示:
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/Project/',
}
2、后端配置
Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
除了 mod_rewrite
,也可以使用 FallbackResource
。
nginx
location / {
try_files $uri $uri/ /index.html;
}
原生 Node.js
const http = require('http')
const fs = require('fs')
const httpPort = 80
http.createServer((req, res) => {
fs.readFile('index.htm', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.htm" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
})
res.end(content)
})
}).listen(httpPort, () => {
console.log('Server listening on: http://localhost:%s', httpPort)
})
四、变迁后带来的问题及解决办法
变迁为history之后,url中不再带“#”符号,美观了,也能解决跨域的问题了。但是引起了新的问题。
1、vue页面中的href、window.open()等使用相对路径时,指向的链接自动变的不对了。
解决办法:
- 使用绝对路径,但是使用绝对路径比较麻烦,不推荐。
- 查看错误链接,和正确的链接对比,对了或者少了什么,然后进行修正。
参考链接:
https://router.vuejs.org/zh/guide/essentials/history-mode.html
https://segmentfault.com/q/1010000010340823/a-1020000011352255