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)配置,表示在匹配給定的地址時,應該使用什麼組件渲染視圖。
將 router 傳入 new Vue() 用於創建根組件,這樣根組件中對應的視圖區域,可以基於 router 中的配置,根據頁面地址顯示不同的內容。當然,這還需要在組件模板中使用 <router-view>
來定義區域。
· 從視圖角度來看,是這樣的:
<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)
小結
其實 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 渲染機制
· 地址變更監聽