单页应用(SPA, Single Page Application)的整个Web系统由一个html文件,经过Ajax和后端进行数据交互,经过一些特殊手段去加载渲染页面的不一样部分,使得无需刷新总体页面,,就像使用app同样,极大的提高了用户使用体验,在Vue生态中,就是利用Vue的核心插件-Vue-Router来实现Web界面的路由跳转,so,本文就是经过学习Vue-Router,了解Vue-Router完成SPA开发的实现具体原理。javascript
先简单说一下路由,早期Web系统路由主要是服务器后端实现,熟悉先后端开发的朋友就知道,在服务器端,经过路由表映射到相应的方法,并执行相应的动做,好比咱们想要请求API数据、新页面、文件等:html
"/" -> goHtml() "/api/users" -> queryUsers()
客户端实现路由主要就有两种方式:前端
1. 基于Web Url的hash 2. 基于Hishtory API
Url中的hash值是“#”号后面的参数值,好比http://www.example.com/index.html#location1
里面的值localtion1, 主要有以下一些特性(以前看到公司项目的Url中带有“#”,虽然有点莫名,但都没去想一想咋来的,汗):vue
1. Url中#后的内容不会发送到服务器; 2. hash值的改变会保存到浏览器的历史浏览记录中,可经过浏览器的回退按钮查看; 3. hash值是可读写的,可经过window.location.hash获取到hash值,利用onhashchange监听hash值的变化(Vue-Router里的hash模式就是在onhashchange中的回调事件中完成路由到界面的更新渲染)。
基于hash模式虽然方便,但带有#号,有同窗就会觉这样的Url有点丑啦,为了和同向服务器后端请求的Url风格保持一致,就可使用Hishtory API模式了。使用基于Hishtory API主要是操做HTML5提供的操做浏览器历史记录API,能够实现地址无刷新更新地址栏,例如:java
// pushState state: 用于描述新记录的一些特性。 // 这个参数会被一并添加到历史记录中,以供之后使用。 // 这个参数是开发者根据本身的须要自由给出的。 // pushState做用至关于修改“#”后面的值 window.history.pushState(state, "title", "/userPage"); // 好比浏览器后退 window.addEventListener("popstate", function(e) { var state = e.state; // do something... }); // 与pushState相对仅进行Url替换而不产生新的浏览记录的replaceState方法 window.history.replaceState(null, null, "/userPage");
咱们来简单对比一下这两种模式的一些特色:node
模式 VS | hash | Hishtory API |
---|---|---|
美观程度 | 公认略丑,带#号 | 相对美观 |
兼容性 | 好 | 须要浏览器支持HTML5 |
错误URL后端支持 | 否 | 是 |
因此两种模式的使用要根据实际项目的需求来定了,接下来是重点啦,Vue-Router内部实现路由就是基于这两种模式!因此提早了解一下前端路由的两种模式算是打个预防针。git
先回忆一下Vue-Router的使用的四步曲,:github
import Vue from 'vue'; import VueRouter from 'vue-router' // 1. 使用Vue-Router插件 Vue.use(VueRouter) // 2. VueRouter实例化 const router = new VueRouter({ model: 'history', // 路由模式 hash\history routes: [ { name: 'xx', path: 'path', component: component } ] }); // 3. 实例化Vue实例时使用该router路由 new Vue({ ... router, }); // 4. 经过Vue.$router.push('xx') 或 router-link进行路由跳转
接下来,就逐次窥探上面四步曲中Vue-Router具体的一些实现细节。web
Vue-Router遵循Vue插件的开发规范,经过调用Vue内部方法Vue.use()对VueRouter进行install(其实是回调VueRouter中所定义的install方法),这一过程完成了VueRouter的挂载工做。vue-router
-> vue\src\core\global-api\use.js Vue.use = function (plugin: Function | Object) { ... plugin.install.apply(plugin, args); ... }
回到VueRouter源码中,分析一下install过程的执行流程:
import View from './components/view' import Link from './components/link' export let _Vue // install实现 export function install (Vue) { // 若是已注册实例,则返回 if (install.installed && _Vue === Vue) return install.installed = true _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { // 父虚拟节点 let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } Vue.mixin({ // 路由钩子函数,在vue执行beforeCreate钩子函数回调时会一块儿调用,由于beforeCreate是一个数组 beforeCreate () { Vue.$options = options if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router // 调用init this._router.init(this) // 调用Vue内部util方法使得当前的url实现响应式化 Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } // 注册实例 registerInstance(this, this) }, destroyed () { registerInstance(this) } }) // 将$router和$route挂载到Vue的原型链上 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) // 执行Vue的选项合并策略 const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created }
能够看出,在install内部主要作了三个事:
const registerInstance = (vm, callVal) => { // 父虚拟节点 let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } }
这个混入至关因而定义,具体执行时等各组件在进行实例化时按照生命周期回调beforeCreate和destoryed这两个钩子函数,destroyed做用是在进行好比路由切换后、实际上做用就是清除上一个展现在router-view中的组件所渲染的内容。
因为但愿篇幅不要太大,否则看起来比较吃力,因此本篇文章先就写到这里,关于实例化和具体使用所涉及到的原理工做后面文章再讨论。在这对本篇文章作一个小结: