相信不少伙伴都听过SPA(单页面应用),SPA指的是在一个应用中只有一个主的index.html页面,区别于多页面应用(多个index.html页面)。SPA的优点如下:
1、交互体验良好
单页应用的内容的改变不需要重新加载整个页面,获取数据也是通过Ajax异步获取,没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象,页面显示流畅,用户的交互体验得到了提升和改善。
2、前后端分离
良好的前后端分离机制,后段无需负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。
3、服务器压力减小,节约服务器资源
单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,所以服务器的吞吐能力会得到大幅的提高,吞吐量可提升几倍甚至几十倍以上。
而对于单页面应用,它是如何在一个页面中实现视图更新的呢? 其中最重要的一个概念就是前端路由,那么接下来就做一个简单的介绍和分析:
vue-router是Vue.js官方提供的路由插件,专门配合vue实现路由跳转功能,需要使用Vue.use()进行安装。区别于传统的通过超链接的形式跳转,实现了在页面无刷新的状态下更新页面视图,用户体验感更好。
vue-router的实现流程主要由如下几个步骤:
1、url发生改变
2、 触发监听事件
3、改变vue-router里面的current变量
4、监视current变量(变量的监视者)
5、获取对应的组件
6、render新组件
7、页面重新渲染
更为详细的底层原理这里便不再赘述,可以直接去看vue-router源码,纸上得来终觉浅,绝知此事要躬行,自己去翻源码捋一捋,会有不一样的收获和成长。
...
export default class VueRouter {
...
static install: () => void;
constructor(options: RouterOptions = {}) {
let mode = options.mode || "hash";
this.fallback =
mode === "history" && !supportsPushState && options.fallback !== false;
if (this.fallback) {
mode = "hash";
}
if (!inBrowser) {
mode = "abstract";
}
this.mode = mode;
switch (mode) {
case "history":
this.history = new HTML5History(this, options.base);
break;
case "hash":
this.history = new HashHistory(this, options.base, this.fallback);
break;
case "abstract":
this.history = new AbstractHistory(this, options.base);
break;
default:
if (process.env.NODE_ENV !== "production") {
assert(false, `invalid mode: ${mode}`);
}
}
}
...
}
上面是vue-router源码片段,由此可知vue-router 有三种路由模式:hash模式、history模式和abstract模式,比较常用的是hash模式和history模式。
一般场景下,hash 和 history 都可以用,除非你更在意颜值或者有强迫症,毕竟/# 符号夹杂在 URL 里我是觉得看起来确实有些不太美丽。如果不想浏览器url里显示很丑的 hash路由网址,我们可以用路由的 history 模式,这种模式利用 history.pushState API 来完成,URL 跳转而无须重新加载页面(后文会详解)。
hash模式与history模式都是通过浏览器接口实现的,除此之外vue-router还为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能。
看到这里,小伙伴可以思考一下,在前端里面如何不刷新页面实现跳转?
目前浏览器中有两种方式可以不刷新页面,实现跳转:1.HTML5的historyAPI,2.通过/#。
我们从上面的vue-router源码里看到一个非常重要的参数–mode,从代码逻辑里可以解读出mode的作用
注:如果浏览器不支持,'history’模式会回滚为’hash’模式。不在浏览器环境下(node环境)运行会强制为’abstract’模式。
history模式
app是Vue根实例,以下是源码调用逻辑。
this.$router.push() -> VueRouter.prototype.push() -> HTML5History.prototype.push() -> History.prototype.transitionTo() -> History.prototype.confirmTransition() -> History.prototype.updateRoute() -> { app._route = route } -> defineReactive(_route) setter -> dep.notify() -> Dep.prototype.notify() -> Watcher.prototype.update() -> Vue.prototype.$nextTick() ->
hash模式
hash -> HashHistory ->
支持window.history -> window.history.pushState()、window.history.go(n)、window.history.replaceState()
不支持window.history -> window.location.hash = path、window.history.go(n)、window.location.replace(url)
this.$router.push() -> VueRouter.prototype.push() -> HashHistory.prototype.push() -> History.prototype.transitionTo() -> History.prototype.confirmTransition() -> History.prototype.updateRoute() -> { app._route = route } -> defineReactive(_route) setter -> dep.notify() -> Dep.prototype.notify() -> Watcher.prototype.update() -> vm.render()
abstract模式
history最大的优点就是长得美观,除此之外还有如下优势:
pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改/#后面的部分,故只可设置与当前同文档的URL;
*
pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串;
*
pushState可额外设置title属性供后续使用;
*
history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误。
大型单页应用最显著特点之一就是采用前端路由系统,通过改变URL,在不重新请求页面的情况下,更新页面视图。
更新视图但不重新请求页面”是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式:
利用URL中的hash(“/#”),修改/#后面的地址,不会重新请求页面,但是修改/#前面的地址会重新请求页面。
//在浏览器的访问历史中增加一个记录,并且不会重新请求页面,只能修改#后面的部分
window.location.hash='#/b/c'
利用History API在 HTML5中新增的方法
//在浏览器的访问历史中增加一个记录,并且不会重新请求页面,只能同源跳转
window.history.pushState({name:'哈哈哈哈'},'首页','http://localhost:8080/s/b')
r o u t e r 和 router和router和route通过Object.defineProperty实现代理,当调用this.$router.push()的时候,会拦截vue原型上的get操作,返回对应的Vue._routerRoot._router的值。
Object.defineProperty(Vue.prototype, "$router", {
get() {
return this._routerRoot._router;
},
});
Object.defineProperty(Vue.prototype, "$route", {
get() {
return this._routerRoot._route;
},
});
Vue.component("RouterView", View);
Vue.component("RouterLink", Link);
之所以视图可以更新,是因为_route是响应式的,即当 _route变化时,会拦截set操作,调用dep.notify()更新视图。
我们知道对于单页应用来讲,理想的使用场景是仅在进入应用时加载index.html,后续在的网络操作通过Ajax完成,不会根据URL重新请求页面,但是难免遇到特殊情况,比如用户直接在地址栏中输入并回车,浏览器重启重新加载应用等。
hash模式仅改变hash部分的内容,而hash部分是不会包含在HTTP请求中的:
http://oursite.com/#/user/id // 如重新请求只会发送http://oursite.com/
故在hash模式下遇到根据URL请求页面的情况不会有问题。
而history模式则会将URL修改得就和正常请求后端的URL一样
http://oursite.com/user/id
在此情况下重新向后端发送请求,如后端没有配置对应/user/id的路由处理,则会返回404错误。
解决方案:在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。同时这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。或者,如果是用 Node.js 作后台,可以使用服务端的路由来匹配 URL,当没有匹配到路由的时候返回 404,从而实现 fallback。
从总结起来,history的缺点如下:
从辩证法的角度说,任何事物都具有两面性,要全面客观的看待事物。history模式和hash模式都有自己的优缺点,项目中采用哪种模式要根据实际情况来定,具体问题具体分析,鞋子合不合穿自己最清楚。
要想从文件系统直接加载Vue单页应用而不借助后端服务器,除了打包后的一些路径设置外,还需确保vue-router使用的是hash模式。
vue-router是vue开发中常见到不能再常见的的一个组件技术了,但是并不是每个人都愿意去花时间去深究它的原理和使用。在工作之余,我花了一些时间去学习它,研究它,加深了对vue-router的理解。每项小的技术都有它的独特之美,我们需要去发现这种美,毕竟万丈高楼平地起,需要牢固的根基也需要每一块砖的堆砌。对于工作和生活,我们也应保持同样的态度,事情不管多小,我们应尽力做好每一件事,做好每一件小事,才能做好能做好更大的事。不积跬步无以至千里。不积小流,无以成江海。
下一步计划:手写vue-router。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/vipshop_fin_dev/article/details/120480353
内容来源于网络,如有侵权,请联系作者删除!