vue-router 源码:路由的安装与初始化
使用
日常我们使用 vue-router 时:
在 template 中我们用 <router-link> 来做路由跳转,用 <router-view> 来做路由跳转后的展示。
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
在 js 中,先用 Vue.use 来安装 vue-router,然后 new 一个 VueRouter 实例,最后将实例注入到 Vue 实例中。
Vue.use(VueRouter)
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
这样就实现了一个路由系统。我把这个过程称为路由的安装与初始化。
那么这个过程里,vue-router 做了什么事情呢?接下来就来一探究竟。
对了,我选的是 vue-router v2.0.1 的代码来阅读。
Flow
在阅读 vue-router 的源码前,我们需要了解一下 Flow。
Flow 是 Facebook 的 JavaScript 静态类型检查工具,语法跟 TypeScript 有点类似。
源码里就用了 Flow 来做静态类型检查。Vue-router 在 Flow 中的自定义类型存放在项目里的 flow 目录下。
想必你会问,为什么不用 TypeScript 而是用 Flow 呢?这个作者的回答是最权威的,戳 这里 了解一下吧。
猜想
在阅读之前,我们先来简单猜想一下,路由安装与初始化会做哪些事情。
- 注册两个组件,
<router-link>和<router-view> - 通过
hash或history来实现前端路由 - 处理作为参数传入的路由,匹配路由
- 将 VueRouter 对象实例注入 Vue 实例中
- ......
install
正式开始阅读代码了,来验证上面的猜想是否正确吧。
在 src/index.js 文件中,有一段这样的代码:
import { install } from './install'
// ...
VueRouter.install = install
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
我们知道调用了 Vue.use 就会默认去调用 install 方法,所以跳转到 src/install.js 文件中。
找到这段代码:
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
}
}
})
这段代码,就是将 VueRouter 对象实例注入 Vue 实例中,赋值给属性 _router,同时创造了另一个属性 _route。
注意,这里还会调用 init 方法,接下来会阅读到。
下划线表示私有属性,如果需要给外部使用,则需要暴露一个方法或外部属性出来:
Object.defineProperty(Vue.prototype, '$router', {
get () { return this.$root._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this.$root._route }
})
这样子,就可以在 Vue 的组件里 this.$router 或 this.$route 的调用。
最后注册了两个全局组件,<router-link> 和 <router-view>。
Vue.component('router-view', View)
Vue.component('router-link', Link)
这里有一个需要注意的点,Vue.use 会调用 install 方法,即以上的代码都会执行,但是 Vue 的 beforeCreate 钩子是在 new Vue 的时候才会执行。
意思就是 new VueRouter 会在 beforeCreate 之前执行。即会先执行 VueRouter 的 constructor 构造函数。
constructor
来看看 VueRouter 的 constructor 构造函数做了哪些事情吧。constructor 的代码不多,主要是初始化一些属性。
constructor (options: RouterOptions = {}) {
this.app = null
this.options = options
this.beforeHooks = []
this.afterHooks = []
this.match = createMatcher(options.routes || [])
let mode = options.mode || 'hash'
this.fallback = mode === 'history' && !supportsHistory
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
}
里面的 createMatcher 先跳过,这又是另一大块,暂时不管。
可以看到后面的代码就是在设置 this.mode,即路由模式。
默认是 hash 模式,如果设置了 history 还得判断支持该种模式不,不支持则下降为默认的模式。如果代码不是运行在浏览器而是在 node 端,则设置为 abstract 模式,这个也先跳过哈哈哈。
在 beforeCreate 会调用 VueRouter 的 init 方法,来看看里面做了什么初始化工作吧。
init
init 方法是这么被调用的:
this._router.init(this)
这里的 this 指向的是 Vue 实例。
再来看看 init 里面的实现(过滤掉部分代码):
init (app: any /* Vue component instance */) {
this.app = app
const { mode, options, fallback } = this
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, fallback)
break
case 'abstract':
this.history = new AbstractHistory(this)
break
default:
assert(false, `invalid mode: ${mode}`)
}
this.history.listen(route => {
this.app._route = route
})
}
init 实现的是,通过刚刚 constructor 设置的 mode 来生成一个新的属性 this.history。
this.history 是根据不同的 mode 来 new 出不同的对象实例。像 history 模式就用 HTML5History,hash 模式就用 HashHistory。
对象里面的实现待以后再深入吧。现在我们只要知道有这么一个新属性 this.history 即可。
this.history 通过调用 listen 方法,将更新 _route 的操作保存起来,在以后更新路由的时候,会执行该操作来更新 _route。讲了跟没讲一样哈哈,没关系,现在只要知道有这代码存在就行了。
回顾
VueRouter 的路由安装与初始化做了哪些事情。按顺序来:
(一)
调用 install 方法,注册两个组件,<router-link> 和 <router-view>。
(二)
new 了 VueRouter 实例,调用 contructor 构造函数,初始化了一些属性,其中包括 mode 属性,用来保存路由模式。
(三)
new 了 Vue 实例,调用其 beforeCreate,将 VueRouter 对象实例注入 Vue 实例中,并调用 install 方法。install 方法则在根据不同的路由模式新增一个 history 属性。history 属性保存的对象里面又是一片天地,待续。