引子
这几天同事在实现内部的页面时,用到了基于 vue 框架的 iview-admin 解决方案,在实际使用时遇到了以下一个问题:
- 在正常操作页面时,浏览器地址栏里会显示当前页面的路由信息,比如 http://example.com/xxx/yyy; 但是在按 F5 刷新该页面的时候,会收到web服务器返回的
invalid method or path
提示。这是为什么?
针对该问题,我好好研究了一番。
问题研究
通过查看 chrome 浏览器的 检查
选项,我们发现刷新时浏览器向服务器发送了路径为 xxx/yyy
的GET 请求,但是服务器并没有对改路由注册处理方法,因此返回了错误。
按照我的理解,xxx/yyy
应该是前端的路由,发往服务器处理是太不合理了;但是浏览器里刷新之后,重新发送 GET 请求似乎也合情合理。经过一番 google,我发现似乎和 vue-router 的一个称为 history 模式的东西有关。
单页面应用SPA
定义
维基百科对SPA的定义:
单页应用(英语:single-page application,缩写SPA)是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。
在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面。页面在过程中的任何时间点都不会重新加载,也不会将控制转移到其他页面。
现在很多 Javascript 框架都是基于单页面的,如 AngularJS、React、Vue.js 等。
SPA与服务端渲染
SPA技术将逻辑从服务器转移到了客户端。这导致Web服务器发展为一个纯数据API或Web服务。这种架构的转变在一些圈子中被称为“瘦服务器架构”,以强调复杂性已从服务端转移到客户端,并认为这最终降低了系统的整体复杂性。
另外一种方式称为服务端渲染,这种设计是服务器在内存中保存必要的客户端所处状态。这种模式下,当任何请求到达服务器(通常因用户操作)时,服务器发送适当的HTML和/或JavaScript,以及具体的更改,以使客户端达到新的期望状态(如添加、删除或更新部分客户端的DOM)。与此同时更新服务器中的状态。这种设计下的大部分逻辑都在服务器上运行,HTML通常也在服务器上呈现。在某些方面,服务器是模拟Web浏览器,接收事件并执行服务器状态下的增量更改,将这些更改自动传播到客户端。
这种方法需要更多的服务器内存和处理能力,但优点是简化的开发模型,因为:1、应用程序通常完全在服务器中编写;2、服务器中的数据和UI状态在相同的内存空间中共享,不需要自定义客户端/服务器通信隧道。
SPA的问题
- 很多搜索引擎爬虫缺乏执行 javascript 的能力,SEO(搜索引擎最优化)成为SPA的问题,由于只有一个index.html的页面,爬虫往往爬不到很多内容
- 根据单页应用(SPA)模型的定义,它只有“单个页面”,因此这打破了浏览器为页面历史记录导航所设计的“前进/后退”功能。当用户按下后退按钮时,可能会遇到可用性障碍,页面可能返回真正的上一个页面,而非用户所期望的上一个页面
- SPA应用需要在一开始就加载所有的页面,因此加载速度往往不快。为了加快加载速度,可以采用一些方法比如多项缓存措施、需要时再加载某些模块(懒加载)
前端路由
路由这个概念最先是后端出现的。在以前用模板引擎开发页面时,经常会看到这样 http://hometown.xxx.edu.cn/bbs/forum.php ,有时还会有带.asp
或.html
的路径,这就是所谓的SSR (Server Side Render),通过服务端渲染,直接返回页面。
简单来说路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。
前端路由的出现要从 ajax 开始。Ajax,全称 Asynchronous JavaScript And XML,是浏览器用来实现异步加载的一种技术方案。在 90s 年代初,大多数的网页都是通过直接返回 HTML 的,用户的每次更新操作都需要重新刷新页面。及其影响交互体验,随着网络的发展,迫切需要一种方案来改善这种情况。有了 Ajax 后,用户交互就不用每次都刷新页面,体验带来了极大的提升,为后来异步交互体验方式的繁荣发展带来了根基。
而异步交互体验的更高级版本就是 SPA,单页应用不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。
vue中为了构建SPA,需要引入前端路由系统,这也就是 Vue-Router 存在的意义。前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。
hash模式和history模式
为了达到不自动发送请求给后端,浏览器当前提供了以下两种模式支持:
hash: 即地址栏 URL 中的 # hash 符号(此 hash 不是密码学里的散列运算)2014年之前广泛应用。
比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。每次 hash 值的变化,还会触发 hashchange
这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。
history : 14年后,因为HTML5标准发布,多了两个 API,pushState
和 replaceState
,通过这两个 API 可以改变 url 地址且不会发送请求。同时还有 onpopstate
事件。通过这些就能用另一种方式来实现前端路由了,但原理都是跟 hash 实现相同的。用了 HTML5 的实现,单页路由的 url 就不会多出一个#
,变得更加美观。
但因为没有 #
号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。
VUE的history模式
vue中对history模式的支持可以参考 vue中HTML5 history模式,需要后端配置做一些处理,即服务端在匹配路由时,如果发现找不到,则不能返回404或者其他错误信息了,而需要特殊处理一下:直接返回/对应的index.html,这相当于重新加载了一次单页面应用,前端收到返回后再根据自己的路由导航到对应的页面。