装置增加全局的 beforeCreate、destroyed 生命周期办法;Vue.prototype 上增加$route和$router 属性;注册 router-view 和 router-link 组件;增加新的生命周期 beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate;beforeCreate 周期中,将路由 VueRouter 实例 router 增加根组件(蕴含 VueRouter 实例选项的组件)上,同时增加路由信息\_route 属性到根组件上,设置该属性响应式。子组件\_routerRoot 属性指向该根组件。Vue.prototy 上的$router和$route 属性值也是来自于根组件的 router 和\_route。根组件的 beforeCreate 周期中调用了 VueRouter 实例的 init 办法进行初始化。function install(Vue) { if (install.installed && _Vue === Vue) { return; } install.installed = true; _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.mixin({ beforeCreate: function beforeCreate() { if (isDef(this.$options.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); }, destroyed: function destroyed() { registerInstance(this); }, }); Object.defineProperty(Vue.prototype, "$router", { get: function get() { return this._routerRoot._router; // this._routerRoot 根组件 }, }); Object.defineProperty(Vue.prototype, "$route", { get: function get() { return this._routerRoot._route; // this._routerRoot 根组件 }, }); Vue.component("RouterView", View); Vue.component("RouterLink", Link); var strats = Vue.config.optionMergeStrategies; // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;}router 实例VueRouter 结构器中,依据配置的路由模式生成对应的 History 实例。调用了 createMatcher 办法,依据选项中的路由生成门路、名称和路由配置项的映射,并返回 matcher,提供 match 和 addRoutes 办法。依据路由模式配置,生成对应的 History 实例。var VueRouter = function VueRouter(options) { if (options === void 0) options = {}; this.app = null; this.apps = []; this.options = options; this.beforeHooks = []; this.resolveHooks = []; this.afterHooks = []; this.matcher = createMatcher(options.routes || [], this); var mode = options.mode || "hash"; this.fallback = mode === "history" && !supportsPushState && options.fallback !== false; if (this.fallback) { mode = "hash"; } if (!inBrowser) { mode = "abstract"; } this.mode = mode; switch (mode) { case "history": this.history = new HTML5History(this, options.base); break; case "hash": this.history = new HashHistory(this, options.base, this.fallback); break; case "abstract": this.history = new AbstractHistory(this, options.base); break; default: if (process.env.NODE_ENV !== "production") { assert(false, "invalid mode: " + mode); } }};RouterView 组件props 的 name 属性匹配对应的路由命名视图。调用 render 函数,渲染以后路由对应的组件。通过 parent.\$route 读取以后的路由信息,而后向上查找,依据 routerView 路由组件标识 routerView,获取以后的路由层级。依据路由层级和命名视图的名称获取对应的组件选项,依据组件选项生成对应的节点(VNode 实例)返回。var View = { name: "RouterView", functional: true, props: { name: { type: String, default: "default", }, }, render: function render(_, ref) { var props = ref.props; var children = ref.children; var parent = ref.parent; var data = ref.data; // used by devtools to display a router-view badge data.routerView = true; // directly use parent context's createElement() function // so that components rendered by router-view can resolve named slots var h = parent.$createElement; var name = props.name; var route = parent.$route; // 读取路由信息 var cache = parent._routerViewCache || (parent._routerViewCache = {}); // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. var depth = 0; var inactive = false; while (parent && parent._routerRoot !== parent) { var vnodeData = parent.$vnode ? parent.$vnode.data : {}; if (vnodeData.routerView) { depth++; } if ( vnodeData.keepAlive && parent._directInactive && parent._inactive ) { inactive = true; } parent = parent.$parent; } data.routerViewDepth = depth; // render previous view if the tree is inactive and kept-alive if (inactive) { var cachedData = cache[name]; var cachedComponent = cachedData && cachedData.component; if (cachedComponent) { // #2301 // pass props if (cachedData.configProps) { fillPropsinData( cachedComponent, data, cachedData.route, cachedData.configProps ); } return h(cachedComponent, data, children); } else { // render previous empty view return h(); } } var matched = route.matched[depth]; // 获取以后层级的组件 var component = matched && matched.components[name]; // 获取该层级下命名视图对应的组件 // render empty node if no matched route or no config component if (!matched || !component) { cache[name] = null; return h(); } // cache component cache[name] = { component: component }; // attach instance registration hook // this will be called in the instance's injected lifecycle hooks data.registerRouteInstance = function (vm, val) { // val could be undefined for unregistration var current = matched.instances[name]; if ((val && current !== vm) || (!val && current === vm)) { matched.instances[name] = val; } }; // also register instance in prepatch hook // in case the same component instance is reused across different routes (data.hook || (data.hook = {})).prepatch = function (_, vnode) { matched.instances[name] = vnode.componentInstance; }; // register instance in init hook // in case kept-alive component be actived when routes changed data.hook.init = function (vnode) { if ( vnode.data.keepAlive && vnode.componentInstance && vnode.componentInstance !== matched.instances[name] ) { matched.instances[name] = vnode.componentInstance; } }; var configProps = matched.props && matched.props[name]; // save route and configProps in cachce if (configProps) { extend(cache[name], { route: route, configProps: configProps, }); fillPropsinData(component, data, route, configProps); } return h(component, data, children); },};route.matched 顺次保留了以后门路对应的组件选项,以上面路由配置为例:/home/tab/list门路对应的 matched 是:[Home, Tab, List]。const router = new Router({ mode: "history", routes: [ { name: "home", path: "/home", component: Home, children: [ { name: "tab", path: "tab", component: Tab, children: [ { name: "list", path: "list", component: List, }, ], }, ], }, ],});实例 init 初始化根组件的 beforeCreate 周期中调用了 VueRouter 实例 init 办法。默认初始门路为“/”,并依据该门路获取对应的路由信息,而后和以后实在的 url 门路比对,更新为以后 url 对应的路由信息,更改\_router 值,因为设置了\_router 属性响应式,且 router-view 读取了该值,当\_router 从新赋值时,就有从新渲染 router-view 组件,加载以后门路对应的组件页面。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." ); this.apps.push(app); // 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 var index = this$1.apps.indexOf(app); if (index > -1) { this$1.apps.splice(index, 1); } // 组件销毁时从apps移除 // 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; } if (!this$1.app) { // clean up event listeners // https://github.com/vuejs/vue-router/issues/2341 this$1.history.teardownListeners(); } }); // main app previously initialized // return as we don't need to set up new history listener if (this.app) { return; } this.app = app; var history = this.history; if (history instanceof HTML5History || history instanceof HashHistory) { var setupListeners = function () { history.setupListeners(); }; history.transitionTo( history.getCurrentLocation(), setupListeners, setupListeners ); } history.listen(function (route) { this$1.apps.forEach(function (app) { app._route = route; // 更新路由 }); });};// 初始路由信息var START = createRoute(null, { path: "/",});路由跳转HTML5History,HashHistory,AbstractHistory 继承了 Histroy,Histroy 实例上的 router 指向 VueRouter 实例。Histroy 提供了 transitionTo 和 confirmTransition 办法,将在路由跳转时调用。/* */var History = function History (router, base) { this.router = router;// VueRouter实例 this.base = normalizeBase(base);// 格式化根底门路 // start with a route object that stands for "nowhere" this.current = START;// "/"对应的路由信息(初始路由信息) this.pending = null; this.ready = false; this.readyCbs = []; this.readyErrorCbs = []; this.errorCbs = []; this.listeners = []; }; History.prototype.transitionTo = function transitionTo ( location,// 以后URL对应路由的门路 onComplete, onAbort ) { var this$1 = this; var route = this.router.match(location, this.current);// 调用VueRouter实例的match办法,返回行将跳转的路由信息,current以后的路由信息 this.confirmTransition( route, function () { var prev = this$1.current; this$1.updateRoute(route);// 更新为跳转后的路由信息 }, ... ); }; History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) { ... }; History.prototype.updateRoute = function updateRoute (route) { this.current = route; this.cb && this.cb(route); }; History.prototype.setupListeners = function setupListeners () { ... }; History.prototype.teardownListeners = function teardownListeners () {// 革除事件监听 ... };var HTML5History = /*@__PURE__*/ (function (History) { //继承了History function HTML5History(router, base) { History.call(this, router, base); this._startLocation = getLocation(this.base); } if (History) HTML5History.__proto__ = History; // 继承History,复用结构器上的办法 HTML5History.prototype = Object.create(History && History.prototype); // 继承History,复用实例上的办法 HTML5History.prototype.constructor = HTML5History;// 继承History,复用结构器 HTML5History.prototype.setupListeners = function setupListeners() { var this$1 = this; if (this.listeners.length > 0) { return; } var router = this.router; var expectScroll = router.options.scrollBehavior; var supportsScroll = supportsPushState && expectScroll; if (supportsScroll) { this.listeners.push(setupScroll()); //增加事件 } var handleRoutingEvent = function () { var current = this$1.current; // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. var location = getLocation(this$1.base); if (this$1.current === START && location === this$1._startLocation) { return; } this$1.transitionTo(location, function (route) { if (supportsScroll) { handleScroll(router, route, current, true); } }); }; window.addEventListener("popstate", handleRoutingEvent); this.listeners.push(function () { window.removeEventListener("popstate", handleRoutingEvent); }); }; HTML5History.prototype.go = function go(n) { // 路由回退或者后退 window.history.go(n); }; HTML5History.prototype.push = function push(location, onComplete, onAbort) { // 路由跳转 var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo( location, function (route) { pushState(cleanPath(this$1.base + route.fullPath)); // 保留页面滚动信息,设置页面跳转url,增加一条记录到history中 handleScroll(this$1.router, route, fromRoute, false); onComplete && onComplete(route); }, onAbort ); }; HTML5History.prototype.replace = function replace( location, onComplete, onAbort ) { var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo( location, function (route) { replaceState(cleanPath(this$1.base + route.fullPath)); // 保留页面滚动信息,批改以后路由,并批改history以后记录 handleScroll(this$1.router, route, fromRoute, false); // 页面滚动 onComplete && onComplete(route); // 跳转胜利回调函数 }, onAbort ); }; /** * 获取路由门路(不蕴含根底门路) */ HTML5History.prototype.getCurrentLocation = function getCurrentLocation() { return getLocation(this.base); }; return HTML5History;})(History);页面跳转路由跳转时先依据以后路由和跳转路由对应的组件,别离提取出须要更新,须要解冻(暗藏或移除),须要激活(创立或显示)的组件。而后顺次调用组件上路由相干的生命周期钩子函数,包含加载异步组件。调用路由生命周期钩子函数后,更新以后的路由数据。最初更新 history 状态,并保留页面的滚动状态。因为对路由数据设置了响应式,更新以后的路由数据会触发页面从新渲染HTML5History.prototype.push = function push(location, onComplete, onAbort) { // 路由跳转 var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo( location, // 跳转选项 function (route) { pushState(cleanPath(this$1.base + route.fullPath)); // 保留页面滚动信息,设置页面跳转url,增加一条记录到history中 handleScroll(this$1.router, route, fromRoute, false); onComplete && onComplete(route); }, onAbort );};History.prototype.transitionTo = function transitionTo( location, // 跳转选项 onComplete, onAbort) { var this$1 = this; var route = this.router.match(location, this.current); // 调用VueRouter实例的match办法,返回跳转选项匹配的路由信息 this.confirmTransition( route, function () { var prev = this$1.current; this$1.updateRoute(route); // 更新为曾经跳转的路由 onComplete && onComplete(route); this$1.ensureURL(); this$1.router.afterHooks.forEach(function (hook) { hook && hook(route, prev); }); // fire ready cbs once if (!this$1.ready) { this$1.ready = true; this$1.readyCbs.forEach(function (cb) { cb(route); }); } }, function (err) { if (onAbort) { onAbort(err); } if (err && !this$1.ready) { this$1.ready = true; this$1.readyErrorCbs.forEach(function (cb) { cb(err); }); } } );};History.prototype.confirmTransition = function confirmTransition( route, onComplete, onAbort) { var this$1 = this; var current = this.current; var abort = function (err) { // changed after adding errors with // https://github.com/vuejs/vue-router/pull/3047 before that change, // redirect and aborted navigation would produce an err == null if (!isRouterError(err) && isError(err)) { if (this$1.errorCbs.length) { 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(); // 确保以后路由和页面的url统一 return abort(createNavigationDuplicatedError(current, route)); } var ref = resolveQueue(this.current.matched, route.matched); // 比对新旧路由对应的组件嵌套信息,判断出哪些组件须要更新,哪些组件须要激活,哪些组件须要解冻 var updated = ref.updated; // 以后路由和行将跳转路由雷同局部(组件)(更新) var deactivated = ref.deactivated; // 以后路由不同局部(组件)(解冻) var activated = ref.activated; // 行将跳转路由不同局部(组件)(激活) var queue = [].concat( // in-component leave guards extractLeaveGuards(deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks extractUpdateHooks(updated), // in-config enter guards activated.map(function (m) { return m.beforeEnter; }), // async components resolveAsyncComponents(activated) // 返回函数function(to, from, next) ); this.pending = route; var iterator = function (hook, next) { if (this$1.pending !== route) { return abort(createNavigationCancelledError(current, route)); } try { // 调用钩子办法 hook(route, current, function (to) { if (to === false) { // next(false) -> abort navigation, ensure current URL this$1.ensureURL(true); abort(createNavigationAbortedError(current, route)); } else if (isError(to)) { 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(createNavigationRedirectedError(current, route)); 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 () { // 调用组件leave、update等钩子函数,异步获取组件选项,创立组件结构器,最初调用这里的回调函数 var postEnterCbs = []; var isValid = function () { return this$1.current === route; }; // wait until async components are resolved before // extracting in-component enter guards var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid); // 异步加载组件选项中的enter钩子办法 var queue = enterGuards.concat(this$1.router.resolveHooks); runQueue(queue, iterator, function () { if (this$1.pending !== route) { return abort(createNavigationCancelledError(current, route)); } this$1.pending = null; onComplete(route); // 更新路由 if (this$1.router.app) { this$1.router.app.$nextTick(function () { postEnterCbs.forEach(function (cb) { cb(); }); }); } }); });};