VueRouter 的实现是一个类,先对它做一个简单地分析,它的定义在 src/index.js 中
export default class VueRouter {static install: () => voidstatic version: stringstatic isNavigationFailure: Functionstatic NavigationFailureType: anystatic START_LOCATION: Routeapp: anyapps: Array<any>ready: booleanreadyCbs: Array<Function>options: RouterOptionsmode: stringhistory: HashHistory | HTML5History | AbstractHistorymatcher: Matcherfallback: booleanbeforeHooks: Array<?NavigationGuard>resolveHooks: Array<?NavigationGuard>afterHooks: Array<?AfterNavigationHook>constructor (options: RouterOptions = {}) {if (process.env.NODE_ENV !== 'production') {warn(this instanceof VueRouter, `Router must be called with the new operator.`)}this.app = null // 根实例this.apps = [] // 保存持有$options.router属性的Vue实例this.options = options // 保存传入的路由配置// 钩子函数this.beforeHooks = []this.resolveHooks = []this.afterHooks = []// 路由匹配器this.matcher = createMatcher(options.routes || [], this)let mode = options.mode || 'hash'// 浏览器不支持history.pushState的情况下,根据传入fallback配置参数决定是否退回到hash模式this.fallback =mode === 'history' && !supportsPushState && options.fallback !== falseif (this.fallback) {mode = 'hash'}if (!inBrowser) {mode = 'abstract'}this.mode = mode // 路由创建的模式// this.history表示路由历史的具体的实现实例,它是根据this.mode的不同实现不同,它有history基类,然后不同的history实现都是继承Historyswitch (mode) {case 'history':this.history = new HTML5History(this, options.base)breakcase 'hash':this.history = new HashHistory(this, options.base, this.fallback)breakcase 'abstract':this.history = new AbstractHistory(this, options.base)breakdefault:if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)}}}// 下一节match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {return this.matcher.match(raw, current, redirectedFrom)}get currentRoute (): ?Route {return this.history && this.history.current}// 参数是Vue实例init (app: any /* Vue component instance */) {process.env.NODE_ENV !== 'production' &&assert(install.installed,`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +`before creating root instance.`)// 存储到this.appsthis.apps.push(app) // 只有根 Vue 实例会保存到 this.app 中,并且会拿到当前的 this.history,根据它的不同类型来执行不同逻辑// set up app destroyed handler// https://github.com/vuejs/vue-router/issues/2639app.$once('hook:destroyed', () => {// clean out app from this.apps array once destroyedconst index = this.apps.indexOf(app)if (index > -1) this.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 reusedif (this.app === app) this.app = this.apps[0] || nullif (!this.app) this.history.teardown()})// main app previously initialized// return as we don't need to set up new history listenerif (this.app) {return}this.app = appconst history = this.historyif (history instanceof HTML5History || history instanceof HashHistory) {const handleInitialScroll = routeOrError => {const from = history.currentconst expectScroll = this.options.scrollBehaviorconst supportsScroll = supportsPushState && expectScrollif (supportsScroll && 'fullPath' in routeOrError) {handleScroll(this, routeOrError, from, false)}}const setupListeners = routeOrError => {history.setupListeners()handleInitialScroll(routeOrError)}// 定义在History的基类中history.transitionTo(history.getCurrentLocation(),setupListeners,setupListeners)}history.listen(route => {this.apps.forEach(app => {app._route = route})})}beforeEach (fn: Function): Function {return registerHook(this.beforeHooks, fn)}beforeResolve (fn: Function): Function {return registerHook(this.resolveHooks, fn)}afterEach (fn: Function): Function {return registerHook(this.afterHooks, fn)}onReady (cb: Function, errorCb?: Function) {this.history.onReady(cb, errorCb)}onError (errorCb: Function) {this.history.onError(errorCb)}push (location: RawLocation, onComplete?: Function, onAbort?: Function) {// $flow-disable-lineif (!onComplete && !onAbort && typeof Promise !== 'undefined') {return new Promise((resolve, reject) => {this.history.push(location, resolve, reject)})} else {this.history.push(location, onComplete, onAbort)}}replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {// $flow-disable-lineif (!onComplete && !onAbort && typeof Promise !== 'undefined') {return new Promise((resolve, reject) => {this.history.replace(location, resolve, reject)})} else {this.history.replace(location, onComplete, onAbort)}}go (n: number) {this.history.go(n)}back () {this.go(-1)}forward () {this.go(1)}getMatchedComponents (to?: RawLocation | Route): Array<any> {const route: any = to? to.matched? to: this.resolve(to).route: this.currentRouteif (!route) {return []}return [].concat.apply([],route.matched.map(m => {return Object.keys(m.components).map(key => {return m.components[key]})}))}resolve (to: RawLocation,current?: Route,append?: boolean): {location: Location,route: Route,href: string,// for backwards compatnormalizedTo: Location,resolved: Route} {current = current || this.history.currentconst location = normalizeLocation(to, current, append, this)const route = this.match(location, current)const fullPath = route.redirectedFrom || route.fullPathconst base = this.history.baseconst href = createHref(base, fullPath, this.mode)return {location,route,href,// for backwards compatnormalizedTo: location,resolved: route}}getRoutes () {return this.matcher.getRoutes()}addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {this.matcher.addRoute(parentOrRoute, route)if (this.history.current !== START) {this.history.transitionTo(this.history.getCurrentLocation())}}addRoutes (routes: Array<RouteConfig>) {if (process.env.NODE_ENV !== 'production') {warn(false, 'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')}this.matcher.addRoutes(routes)if (this.history.current !== START) {this.history.transitionTo(this.history.getCurrentLocation())}}}
实例化 VueRouter 后会返回它的实例 router,在 new Vue 的时候会把 router 作为配置的属性传入
上一节讲 beforeCreate 混入的时候有这么一段代码
beforeCreate() {if (isDef(this.$options.router)) {// ...this._router = this.$options.routerthis._router.init(this)// ...}}
所以组件在执行 beforeCreate 钩子函数的时候,如果传入了 router 实例,都会执行 router.init 方法
history.transitionTo代码在 src/history/base.js
transitionTo (location: RawLocation,onComplete?: Function,onAbort?: Function) {let route// catch redirect option https://github.com/vuejs/vue-router/issues/3201try {// 调用了this.matcher.match方法去做匹配route = this.router.match(location, this.current)} catch (e) {this.errorCbs.forEach(cb => {cb(e)})// Exception should still be thrownthrow e}// ...}
大致对 VueRouter 类有了大致了解,知道了它的一些属性和方法,同时了解到在组件的初始化阶段,执行到 beforeCreate 钩子函数的时候会执行 router.init 方法,然后又会执行 history.transitionTo 方法做路由过渡,进而引出了 matcher 的概念
