一、前言
单页面应用(SPA)的核心思想之一,就是更新视图而不重新请求页面,简单来说,它在加载页面时,不会加载整个页面,只会更新某个指定的容器中的内容。对于大多数单页面应用,都推荐使用官方支持的vue-router。
在实现单页面前端路由时,提供了两种方式,分别是hash模式和history模式,根据mode参数来决定采用哪一种方式。
const router = new VueRouter({ routes, mode: "history" // 或者 "hash" })
二、hash模式
http://example.com/#/home
Hash模式是Vue.js中默认的URL管理模式。在Hash模式下,URL中的#符号用于标识页面状态的变化。当页面中的路由发生改变时,Vue.js会使用Hash模式来更新URL,并在页面中渲染相应的组件。
- Hash模式是通过监听hashChange事件来实现的,前端js把当前hash地址对应的组件渲染到浏览器中。
- 在地址栏URL上有#号。
- 通常用于单页面应用(SPA),支持页面内的导航,不会刷新页面,也不会影响服务器。
- Hash出现在URL中,但不包括在http请求中,对后端没有影响。
- 在进行URL跳转时,如果只改变hash,不会重新加载页面。
hashchange 事件:
当hash值改变时会触发这个事件,通过 location.hash 获取到最新的 hash 值
注意:
- hash 值的变化不会导致浏览器像服务器发送请求
- location.hash可以获取hash值
- hashchange是hash值发生改变的调用的函数
语法:
window.onhashchange = function () { console.log(location.hash); }; // 或 window.addEventListener('hashchange', ()=>{ console.log(location.hash); });
三、history模式
http://example.com/home
History模式是另一种URL管理模式,它使用HTML5的History API来实现URL的变化和页面的刷新。在History模式下,URL中的#符号被移除,取而代之的是通过History API来管理页面的状态。
History模式的优点在于它能够实现平滑的页面过渡,提高用户体验。同时,由于URL中不包含#符号,因此也适用于搜索引擎优化。然而,History模式需要在服务器端进行额外的配置,以确保能够正确地处理不同的URL路径请求。
- History模式是通过调用history.pushState方法(或者replaceState)并且监听popstate事件来实现的。history.pushState会追加历史记录,并更换地址栏地址信息,但是页面不会刷新。
- 需要服务器端做额外的配置,否则可能会出现刷新页面时的404错误。
- 在进行URL跳转时,即使只改变hash,也需要重新加载页面(如果当前页面不是SPA)。
- 支持浏览器的前进、后退功能。
方法:
1、history.go():
通过当前页面的相对位置从浏览器历史记录(会话记录)异步加载页面。
参数:
- 参数为 -1 的时候为上一页,
- 参数为 1 的时候为下一页。
- 当你指定了一个越界值,例如:当会话历史记录中没有之前访问的页面时,则传参的值为 -1,那么这个方法没有任何效果也不会报错。
- 调用没有参数的 go() 方法或者参数值为 0 时,重新载入当前页面。
- Internet Explorer 允许你指定一个字符串,而不是整数,以转到历史记录列表中的特定 URL。
2、history.back():
此异步方法转到浏览器会话历史的上一页,与用户单击浏览器的 Back 按钮的行为相同。等价于 history.go(-1)。
调用此方法回到会话历史的第一页之前没有效果并且不会引发异常。
3、history.forward():
此异步方法转到浏览器会话历史的下一页,与用户单击浏览器的 Forward 按钮的行为相同。等价于 history.go(1)。
调用此方法超越浏览器历史记录中最新的页面没有效果并且不会引发异常。
4、History.replaceState()
替换当前页在历史记录中的信息
语法:
history.replaceState(stateObj, title[, url])
参数:
stateObj:状态对象是一个 JavaScript 对象,它与传递给 replaceState 方法的历史记录实体相关联。
title:大部分浏览器忽略这个参数, 将来可能有用。在此处传递空字符串应该可以防止将来对方法的更改。或者,你可以为该状态传递简短标题。
url:可选,历史记录实体的 URL. 新的 URL 跟当前的 URL 必须是同源; 否则 replaceState 抛出一个异常。
5、History.pushState()
向历史记录中追加一条记录
语法:
pushState(stateObj, title, url)
参数:
- stateObj:一个用于表示历史记录状态的JavaScript对象。这个对象的内容会被添加到浏览器的历史堆栈中。这个参数是可选的。
- title:为新的历史记录条目提供标题。这个参数通常可以设置为null,因为大多数浏览器在显示历史记录列表时并不使用这个标题。
- url:可选,要添加到历史记录中的新URL。这个 URL 必须与当前页面处于同一个域中,否则浏览器可能会阻止这个操作。
优势:
- pushState()设置的新的url可以是于当前url同源的任意url,而hash只可修改#后面的部分,因此只能设置与当亲url同文档的url。
- pushState()设置的新的url可以与当前url一摸一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来的不一样才会触发动作将记录添加到栈中。
- pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串。
当你在SPA中使用history.pushState()或者history.replaceState()时,你可能会希望更改浏览器的URL,但是不刷新页面。这可以让你的SPA具有更好的用户体验,因为用户可以点击浏览器的后退和前进按钮来导航,而不需要在你的应用中进行额外的点击。
popState 事件
每当同一个文档的浏览历史(即history)出现变化时,就会触发popState事件。
注意:
- 仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器后退和前进按钮时,或者使用js调用back、forward、go方法时才会触发。
- 该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件不会被触发。
- 页面第一次加载的时候,浏览器不会触发popState事件。
语法:
window.onpopstate = function (event) { console.log('location: ' + document.location); console.log('state: ' +JSON.stringify(event.state)); }; // 或 window.addEventListener('popstate', function(event) { console.log('location: ' + document.location); console.log('state: ' + JSON.stringify(event.state)); });
四、nginx配置
hash模式下,仅hash符号#之前的内容会被包含在请求中,比如http://www.example.com/home 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误;
history模式下,前端的url必须和实际后端发起请求的url一致,如http://example.com/about/id 。如果后端缺少对/about/id 的路由处理,将返回404错误。
在Vue.js中,可以使用vue-router库来实现Hash模式和History模式。通过配置vue-router的mode属性,可以选择使用Hash模式或History模式。需要注意的是,项目部署生产后,hash 模式能够正常使用,而 history 模式访问不到资源。hash 的改变不会发生请求,因此不影响服务器端,所以 nginx 不会拦截,而 history 模式则需要设置可访问的配置。
比如http://www.example.com/home 如果不进行配置,那么 nginx 默认会去找服务器目录下的 home 文件,而我们想要的效果则是依旧寻找 index 文件,home 则交给前端去处理,nginx 使用 try_files 配置即可实现该效果。
location / { root /www/wwwroot; index index.html; 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.html', 'utf-8', (err, content) => { if (err) { console.log('We cannot open "index.html" 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) })
六、注意
给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后再给出一个 404 页面。
const router = new VueRouter({ mode: 'history', routes: [ { path: '*', component: NotFoundComponent } ] })
或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。
更多详情请查阅 Vue 服务端渲染文档。
七、如何选择合适的路由模式
使用 Hash 模式:
- 如果你的项目不需要考虑兼容性问题,或者需要在旧版浏览器中支持路由功能。
- 如果你希望简化部署过程,只需将静态文件部署到服务器即可。
使用 History 模式:
- 如果你希望 URL 更加美观、简洁,不希望在 URL 中出现 # 符号。
- 如果你可以进行服务器配置,确保在直接访问 URL 时返回正确的页面。
- 如果你的项目不需要考虑旧版浏览器的兼容性问题。