想在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