想在Vue應用中使用VueRouter, 咱們首先須要安裝VueRouter。 方法以下:vue
Vue.use(VueRouter) const router = new VueRouter(options) new Vue({ el: '#app', data: state, router, //將路由器對象router做爲一個選項添加到Vue根實例中 render: h => h(AppLayout), })
安裝VueRouter,分爲如下兩步:node
Vue.use經過調用VueRouter 插件中的install方法把VueRouter 安裝到Vue。咱們從VueRouter的install方法入手看一下安裝流程react
作了如下兩件事:git
若是已經安裝,退出函數github
2.1 經過Vue.mixin更新Vue對象的beforeCreate和destroyed 鉤子vue-router
2.2 經過Object.defineProperty 給Vue對象添加$router 和 $route 屬性。數組
2.3 在Vue對象上註冊RouterView 和RouterLink 組件app
2.4 給Vue對象添加路由鉤子函數 beforeRouteEnter, beforeRouteLeave和 beforeRouteUpdate。異步
具體實現以下:async
var _Vue; function install(Vue) { if (install.installed && _Vue === Vue) { return } // 已安裝過,退出函數 install.installed = true; // installed 標記已安裝 //保存Vue,同時用於檢測是否重複安裝 _Vue = Vue; // var isDef = function (v) { return v !== undefined; }; var registerInstance = function (vm, callVal) { var i = vm.$options._parentVnode; if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal); } }; // 更新Vue對象中的beforeCreate和destroyed 鉤子 Vue.mixin({ beforeCreate: function beforeCreate() { if (isDef(this.$options.router)) { // 調用Vue構造函數建立實例時傳入了router選項(根組件) this._routerRoot = this; this._router = this.$options.router; this._router.init(this); Vue.util.defineReactive(this, '_route', this._router.history.current); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; } registerInstance(this, this); //把RouterView添加到虛擬Node }, destroyed: function destroyed() { registerInstance(this);// 從虛擬node中移除RouterView } }); // 給Vue對象添加$router屬性 Object.defineProperty(Vue.prototype, '$router', { get: function get() { return this._routerRoot._router } }); // 給Vue對象添加$route屬性 Object.defineProperty(Vue.prototype, '$route', { get: function get() { return this._routerRoot._route } }); Vue.component('RouterView', View); //註冊RouterView組件,對應模板中的<router-view /> Vue.component('RouterLink', Link);//註冊 RouterLink組件,對應模板中的<router-link /> //給Vue對象添加路由鉤子函數 beforeRouteEnter, beforeRouteLeave, beforeRouteUpdate var strats = Vue.config.optionMergeStrategies; // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created; }
new Vue({ el: '#app', data: state, router, //將路由器對象router做爲一個選項添加到Vue根實例中 render: h => h(AppLayout), })
因爲 調用install 方法時,更新了Vue對象構造器, 那麼調用new Vue(options) 構建Vue根實例時,
每一個Vue實例的初始化階段都會在beforeCreate鉤子中更新路由相關的操做,銷燬階段在destroyed 鉤子中銷燬路由相關的虛擬節點
每一個Vue實例中都有$router 和 $route 屬性。每一個Vue實例均可以經過this.$router 訪問全局的VueRouter實例,經過this.$route訪問當前路由記錄 。
每一個Vue實例都能使用RouterView 和RouterLink 組件
每一個Vue實例包含路由鉤子函數, 可用於組件內路由導航守衛。
若是當前Vue實例是根實例
那麼給當前Vue實例添加_routerRoot 屬性, _router 屬性 和 _route 屬性
因爲_routerRoot 指向當前Vue實例, 那麼 _routerRoot 中就包含了 _router屬性 和 _route屬性。
若是不是根實例,
那麼給當前Vue實例添加_routerRoot 屬性, _routerRoot 是從父實例中獲取的。
這樣一來全部Vue實例就都有了_routerRoot 屬性 。全部Vue實例 _routerRoot 屬性都是從相同的地方獲取值。
Vue.mixin({ beforeCreate: function beforeCreate() { //混淆進Vue的beforeCreacte鉤子中 if (isDef(this.$options.router)) { // 調用Vue構造函數建立實例時傳入了router選項(根組件) this._routerRoot = this;// this._routerRoot 指向當前Vue實例 this._router = this.$options.router; this._router.init(this); // 調用VueRouter實例的init方法進行初始化導航 //使Vue根實例中的_route響應式 //history.current記錄當前路由對象信息 Vue.util.defineReactive(this, '_route', this._router.history.current); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; } registerInstance(this, this); //把RouterView組件須要展現的視圖對應的虛擬節點添加到虛擬節點樹 }, destroyed: function destroyed() { //混淆進Vue的destroyed鉤子中 registerInstance(this); // 把RouterView組件須要展現的視圖對應的虛擬節點從虛擬節點樹移除 } });
Vue實例的$router屬性 和 $route 屬性都從Vue實例的_routerRoot屬性中獲取值。
// 給Vue對象添加$router屬性 Object.defineProperty(Vue.prototype, '$router', { get: function get() { return this._routerRoot._router } }); // 給Vue對象添加$route屬性 Object.defineProperty(Vue.prototype, '$route', { get: function get() { return this._routerRoot._route } });
下面咱們看一下如何調用VueRouter實例的init方法進行初始化導航。
作了如下幾件事:
在VueRouter實例的apps屬性中記錄調用當前VueRouter實例的全部Vue實例。
在VueRouter實例的app屬性中記錄調用當前VueRouter實例的Vue實例。
history.getCurrentLocation()獲取當前路徑 eg : '/login'
VueRouter.prototype.init = function init(app /* Vue component instance */) { var this$1 = this; assert( install.installed, "not installed. Make sure to call `Vue.use(VueRouter)` " + "before creating root instance." ); //1. 檢查VueRouter安裝 this.apps.push(app);// 2. 把當前Vue實例添加到VueRouter實例的apps屬性中 // set up app destroyed handler // https://github.com/vuejs/vue-router/issues/2639 app.$once('hook:destroyed', function () { // clean out app from this.apps array once destroyed // 若是當前Vue實例調用了destroyed鉤子函數。 那麼從VueRouter實例的apps屬性中刪除當前Vue實例 var index = this$1.apps.indexOf(app); if (index > -1) { this$1.apps.splice(index, 1); } // ensure we still have a main app or null if no apps // we do not release the router so it can be reused if (this$1.app === app) { this$1.app = this$1.apps[0] || null; } }); // main app previously initialized // return as we don't need to set up new history listener if (this.app) { return } this.app = app; // 更新VueRouter實例中的app屬性指向當前Vue實例 var history = this.history; // 根據router的mode不一樣,HTML5History對象,HashHistory對象,AbstractHistory對象 if (history instanceof HTML5History) { // history是HTML5History對象, mode == 'history' //history.getCurrentLocation() 獲取當前路徑'/' history.transitionTo(history.getCurrentLocation()); //導航到當前路徑對應的頁面 } else if (history instanceof HashHistory) { // history是HashHistory對象, mode == 'hash' var setupHashListener = function () { history.setupListeners(); }; history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ); } //監聽記錄會話歷史的history對象。若是路由發生變化,更新apps數組中每一個Vue實例的_route 屬性 history.listen(function (route) { this$1.apps.forEach(function (app) { app._route = route; }); }); };
經過代碼能夠看到 VueRouter中實現導航的使用transitionTo方法。
做用:
導航到指望的路由
參數:
location: 是一個字符串路徑
onComplete: 一個回調函數,在確認導航後執行
onAbort:一個回調函數,在終止導航解析(導航到相同的路由, 或在當前導航完成以前導航到另外一個不一樣的路由)時執行
工做流程:
2.1 若是確認要導航到目的路由,執行如下操做
a) 調用updateRoute更新路由
b) 若是存在onComplete, 調用onComplete回調函數
c) 調用ensureURL進行導航
d) 若是尚未執行須要在完成初始化導航後須要調用的回調函數,那麼就執行回調,這些回調函數經過onReady註冊,保存在readyCbs中
2.2 若是終止導航解析,執行如下操做
a) 若是存在onAbort , 調用onAbort 回調函數
b) 若是尚未執行 初始化路由解析運行出錯時須要調用的回調函數,那麼就執行回調。這些回調函數經過onReady定義,保存在readyErrorCbs中
具體實現以下:
History.prototype.transitionTo = function transitionTo( location, onComplete, onAbort ) { var this$1 = this; // 獲取目的路由對象 var route = this.router.match(location, this.current); this.confirmTransition( // 確認導航 route, function () { // 進行導航 this$1.updateRoute(route); //更新路由 onComplete && onComplete(route); this$1.ensureURL();//進行導航 // fire ready cbs once if (!this$1.ready) {//沒有執行須要在完成初始化導航後須要調用的回調函數 this$1.ready = true; this$1.readyCbs.forEach(function (cb) { cb(route); }); } }, function (err) { // 終止導航 if (onAbort) { // 若是調用transitionTo 傳入的參數中有回調函數 onAbort(err); } if (err && !this$1.ready) {//沒有執行初始化路由解析運行出錯時須要調用的回調函數 this$1.ready = true; this$1.readyErrorCbs.forEach(function (cb) { cb(err); }); } } ); };
confirmTransition
做用:
確認導航
參數:
route: 要確認的路由對象
onComplete: 一個回調函數,在確認導航後執行
onAbort:一個回調函數,在終止導航解析(導航到相同的路由, 或在當前導航完成以前導航到另外一個不一樣的路由)時執行
工做原理:
要解析的目的路由對象和當前路由對象是否相同,
調用ensureURL直接導航到當前路由
調用 abort 來中止導航解析流程
調用全局後置守衛afterEach
具體實現以下:
History.prototype.confirmTransition = function confirmTransition(route, onComplete, onAbort) { var this$1 = this; var current = this.current; var abort = function (err) { // after merging https://github.com/vuejs/vue-router/pull/2771 we // When the user navigates through history through back/forward buttons // we do not want to throw the error. We only throw it if directly calling // push/replace. That's why it's not included in isError if (!isExtendedError(NavigationDuplicated, err) && isError(err)) { if (this$1.errorCbs.length) { // this$1.errorCbs 是經過onError 註冊的回調 this$1.errorCbs.forEach(function (cb) { cb(err); }); } else { warn(false, 'uncaught error during route navigation:'); console.error(err); } } onAbort && onAbort(err); }; // 若是目的路由對象和當前路由對象相同--導航重複 if ( isSameRoute(route, current) && // in the case the route map has been dynamically appended to route.matched.length === current.matched.length ) { //那麼直接導航到當前路由 this.ensureURL(); return abort(new NavigationDuplicated(route))//中止導航解析流程 } var ref = resolveQueue( this.current.matched, route.matched ); var updated = ref.updated; var deactivated = ref.deactivated; var activated = ref.activated; // 把守衛鉤子存在queue中,按順序執行 var queue = [].concat( // in-component leave guards -- 在失活的組件裏調用離開路由守衛beforeRouteLeave extractLeaveGuards(deactivated), // global before hooks -- 調用全局前置守衛beforeEach this.router.beforeHooks, // in-component update hooks -- 在重用的組件裏調用更新路由守衛beforeRouteUpdate extractUpdateHooks(updated), // in-config enter guards -- 在路由配置裏調用beforeEnter activated.map(function (m) { return m.beforeEnter; }), // async components -- 解析異步組件 resolveAsyncComponents(activated) ); this.pending = route; var iterator = function (hook, next) { if (this$1.pending !== route) { return abort() } try { hook(route, current, function (to) { if (to === false || isError(to)) { // next(false) -> abort navigation, ensure current URL this$1.ensureURL(true); abort(to); } else if ( typeof to === 'string' || (typeof to === 'object' && (typeof to.path === 'string' || typeof to.name === 'string')) ) { // next('/') or next({ path: '/' }) -> redirect abort(); if (typeof to === 'object' && to.replace) { this$1.replace(to); } else { this$1.push(to); } } else { // confirm transition and pass on the value next(to); } }); } catch (e) { abort(e); } }; runQueue(queue, iterator, function () {//執行完queue 中的鉤子函數後 var postEnterCbs = []; var isValid = function () { return this$1.current === route; }; // wait until async components are resolved before // extracting in-component enter guards -- 在被激活的組件裏調用beforeRouteEnter var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid); //this$1.router.resolveHooks 調用全局解析守衛beforeResolve var queue = enterGuards.concat(this$1.router.resolveHooks); runQueue(queue, iterator, function () { if (this$1.pending !== route) { return abort() //中止導航解析流程 } this$1.pending = null; onComplete(route); //導航被確認,調用在transitionTo中定義的確認後的回調函數 if (this$1.router.app) { this$1.router.app.$nextTick(function () { //在DOM更新以後調用回調函數, 這些回調函數是經過beforeRouteEnter守衛傳給 next 的 postEnterCbs.forEach(function (cb) { cb(); }); }); } }); }); };
更新路由, 並調用全局後置守衛afterEach
History.prototype.updateRoute = function updateRoute(route) { var prev = this.current; //更新保存當前路由記錄的變量 this.current this.current = route; this.cb && this.cb(route); //調用全局後置守衛afterEach this.router.afterHooks.forEach(function (hook) { hook && hook(route, prev); }); };
調用全局後置守衛afterEach