vue-router 是 Vue.js 官方的路由庫,本着學習的目的,我對 vue-router 的源碼進行了閱讀和分析,分享出來給其餘感興趣的同窗作個參考吧。javascript
文檔:vue-router 官方中文教程vue
咱們分別從不一樣的視角來看 vue-router。java
從開發者角度來看,是這樣的:git
var router = new VueRouter({ routes: [ {path: '/foo', component: {template: '<div>FOO</div>'}}, {path: '/bar', component: {template: '<div>BAR</div>'}} ] }) var vm = new Vue({ el: '#app', router: router })
咱們建立一個 router
,傳入的 routes
中的每一項即爲一條路由(route)配置,表示在匹配給定的地址時,應該使用什麼組件渲染視圖。github
將 router
傳入 new Vue()
用於建立根組件,這樣根組件中對應的視圖區域,能夠基於 router
中的配置,根據頁面地址顯示不一樣的內容。固然,這還須要在組件模板中使用 <router-view>
來定義區域。vue-router
從視圖角度來看,是這樣的:app
<div id="app"> ... <router-view></router-view> ... </div>
頁面地址變動後,<router-view>
對應的區域會更新爲地址匹配的組件。例如,路徑是 /foo
則對應區域顯示 FOO,路徑是 /bar
則顯示 BAR,路徑沒有匹配的組件時,則不顯示內容。函數
從數據角度來看,是這樣的:工具
vm + _router | $router - history - matcher + _route | $route - matched
vm.$router
引用當前組件對應的 router
對象,該對象在初始化時(在 vm
建立過程當中執行初始化),會啓動對頁面地址變動的監聽,從而在變動時更新 vm
的數據($route
),進而觸發視圖的更新。
如何實現對地址變動的監聽?
對於缺省的 HashHistory
模式(也就是基於頁面地址的 hash 部分來實現路由功能,如 http://example.com/path#/foo
、http://example.com/path#/bar
),是經過監聽 hashChange
事件來實現:
window.addEventListener('hashchange', () => { // this.transitionTo(...) })
這個動做是何時執行的呢?
是在 router.init()
(源碼)中調用的,而 router.init()
則是在根組件建立時(源碼)調用的。
而 Vue 組件在建立時,又怎麼會去調用 router.init()
呢?
這是因爲 vue-router 將自身做爲一個插件安裝到了 Vue,經過 Vue.mixin()
註冊了一個 beforeCreate()
鉤子函數,從而在以後全部的 Vue 組件建立時都會調用該鉤子函數,給了檢查是否有 router
參數,從而進行初始化的機會。進而經過層層調用執行了監聽 hashchange
事件的動做。
整理一下:
new Vue()
執行 vue-router 注入的 beforeCreate
鉤子函數
執行 router.init(vm)
執行 history.setupListeners()
,註冊事件監聽
地址變動如何通知到 vm
?
這個過程比較簡單,hashchange
時,執行 history.transitionTo(...)
,在這個過程當中,會進行地址匹配,獲得一個對應當前地址的 route
,而後將其設置到對應的 vm._route
上。
只是進行了賦值,爲何 vm
就能夠感知到路由的改變了呢?
答案在 vue-router 注入 Vue 的 beforeCreate
鉤子函數中(源碼):
Vue.util.defineReactive(this, '_route', this._router.history.current)
採用與 Vue 自己數據相同的「數據劫持」方式,這樣對 vm._route
的賦值會被 Vue 攔截到,而且觸發 Vue 組件的更新渲染流程。
地址變動若是同步視圖更新?
接上一步,vm._route
已經接收到路由的變動,從而觸發視圖更新。而當視圖更新進一步調用到 <router-view>
的 render()
時,即進入了 <router-view>
的處理(源碼)。
<router-view>
的 render()
採用函數調用(h()
)模式,而非經過模板生成。這也是 Vue2 支持的定義組件渲染邏輯的方式,相似 React 的 render()
。採用這種模式的好處是能夠徹底利用 JavaScript 的能力來編寫邏輯,沒必要受制於 Vue 的類 HTML 模板語法。
這裏的主要處理邏輯是從根組件中取出當前的路由對象(parent.$route
),而後取得該路由下對應的組件,而後交由該組件進行渲染:
return h(component, data, children)
這其中還涉及 <router-view>
嵌套的處理,不過主要邏輯就是這樣了。
其實 vue-router 從 <router-view>
的實現來看,就是一個具備特定功能的 Vue 組件而已,不過要配合根組件的 router 發揮做用。但總體仍是很「響應式」的,也是蠻「Vue風格」的。
vue-router 以插件方式「侵入」Vue,從而支持一個額外的 router
屬性,以提供監聽並改變組件路由數據的能力。這樣每次路由發生改變後,能夠同步到數據,進而「響應式」地觸發組件的更新。
<router-view>
做爲根組件下的子組件,從根組件那裏能夠獲取到當前的路由對象,進而根據路由對象的組件配置,取出組件並用其替換當前位置的內容。這樣,也就完成整個路由變動到視圖變動的過程。
路由變動到視圖變動的過程整理爲:
hashchange --> match route --> set vm._route --> <router-view> render() --> render matched component
實現過程當中的技術點包括:
Vue 插件機制
響應式數據機制
Vue 渲染機制
地址變動監聽
我寫了一個應用 Vue.js、vue-router 以及其餘 Vue 相關工具(Vuex、vue-loader)的示例,感興趣能夠看下:luobotang/vue-demo - github。