vue-router 源码分析(一) - 初始化和安装
Vue-router使用demo
<div id="app"> <h1>Hello App!</h1> <p> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <router-view></router-view> </div> const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] Vue.use(VueRouter) const router = new VueRouter({ routes }) const app = new Vue({ router }).$mount('#app')
new VueRouter 发生了什么?
VueRouter
是index.js
暴露的一个类, 所以我们看看他的构造函数都做了什么:
constructor (options: RouterOptions = {}) { this.app = null this.apps = [] this.options = options // 开发者传入的 { routes } 对象 this.beforeHooks = [] // beforeEach钩子函数 this.resolveHooks = [] // beforeResolve钩子函数 this.afterHooks = [] // afterEach钩子函数 // 生成matcher对象, 包含两个方法 {addRoutes, match} this.matcher = createMatcher(options.routes || [], this) // mode默认是hash let mode = options.mode || 'hash' // IE9 自动降级成hash模式 this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false if (this.fallback) { mode = 'hash' } if (!inBrowser) { mode = 'abstract' } this.mode = mode // 根据开发者传入的mode, 构造不同的类实例赋值给this.history 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}`) } } }
显而易见, 构造函数主要是做了一些类属性的初始化工作, 包括生成matcher
对象, 以及根据mode
初始化相应的history类, 这里暂时不探究着这些属性有何意义, 我们只需知道, 开发者按规范传入了一个{routes}
对象, 经过一系列的操作, 我们会得到一个VueRouter
实例router
, 而后, 我们将router
传入到new Vue的初始化配置项中, 了解vue
的vm.$options
属性的同学都知道, 我们可以在根实例通过this.$options.router
取得router
, 而这一步的实现跟vue.use(VueRouter)有着莫大的干系.
install函数
众所周知, vue.use
是vue
使用插件的方式, vue
会调用插件的install
方法, 而VueRouter的install 函数长啥样呢?
注: 为方便阅读, 代码有删减 export function install (Vue) { // 重复安装检测 if (install.installed && _Vue === Vue) return install.installed = true // 小技巧, _Vue保留Vue的引用, 将_Vue导出, 整个项目即可使用Vue _Vue = Vue // 全局混入beforeCreate钩子函数 Vue.mixin({ --- }) Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) Vue.component('RouterView', View) Vue.component('RouterLink', Link) }
install函数它干了一件很重要的事, Vue.mixin(...)
, 全局混入了一个钩子函数, 每个组件都会走一遍这个钩子函数, 而这个钩子函数做了些什么呢?
beforeCreate () { // 初始化根实例上的router, 并定义响应式的_route 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 } }
我们知道只有根实例上才有this.$options.router
, 所以根实例初始化的时候, if条件 成立, 就会走到里面的代码:
this._routerRoot
就是根实例;this._router
就是router
(VueRoute
实例);- 调用
Vue.util.defineReactive
在根实例上注册了一个响应式的属性_route
; - 调用
router
实例上的init
方法(这个以后再讲).
接着, 当其他vue实例初始化的时候, 例如, 我们一般会使用的<App/>
组件, 在它的$options上
就没有router
属性, 所以它会走到else逻辑: 如果当前存在父实例, 则this._routerRoot
等于this.$parent._routerRoot
, 不存在的话, this._routerRoot
就指向当前实例, 这就意味着, 所有实例的_routerRoot
在经历过beforeCreate
这个钩子函数以后, 实例的this._routerRoot
都会指向根实例的this
, 也就是说所有实例的this._routerRoot
都保留了对根实例的引用. 这有什么用呢? 且看以下代码:
Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } })
答案已经显而易见, 通过es5的Object.defineProperty
, 在Vue.prototype
上定义了$router
的取值函数, 返回根实例上的_router
, 也就是VueRouter
的实例router
, 还定义了$route
的取值函数, 返回的是根实例上的_route
, 因此, 我们可以在任何一个组件使用this.$router
就可以拿到router
对象, 可以使用router
上的方法、属性.
最后, intall 方法还注册了两个组件, <router-view/>
和<router-link/>
:
Vue.component('RouterView', View) Vue.component('RouterLink', Link)
下回分解
createMatcher
函数实现分析.