單頁應用(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後端支持 | 否 | 是 | git
因此兩種模式的使用要根據實際項目的需求來定了,接下來是重點啦,Vue-Router內部實現路由就是基於這兩種模式!因此提早了解一下前端路由的兩種模式算是打個預防針。github
先回憶一下Vue-Router的使用的四步曲,:vue-router
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具體的一些實現細節。後端
Vue-Router遵循Vue插件的開發規範,經過調用Vue內部方法Vue.use()對VueRouter進行install(其實是回調VueRouter中所定義的install方法),這一過程完成了VueRouter的掛載工做。
-> 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中的組件所渲染的內容。
因爲但願篇幅不要太大,否則看起來比較吃力,因此本篇文章先就寫到這裏,關於實例化和具體使用所涉及到的原理工做後面文章再討論。在這對本篇文章作一個小結:
介紹了install方法的一些主要工做職能:
另外,歡迎去本人git 可相互學習和star。