Router的从初始化到触发更新时候的流程
如果没有相应的Router
源码阅读经历(如React Router
的阅读经历),那么可能不需要通过了解整个流程就可以知道大致的实现方式,但是对于没有类似的经历的,那么很难做到窥一斑而知全豹
。如果一开始就直接按点来进行讲解的话,那么很多时候都会很懵,因为不知道作用。所以在进行讲解之前,先看一下Vue Router
的工作流程。
初始化
在项目初始化的时候,会先进行VueRouter
的安装,只需要记住安装的时候会在Vue
中混入了一个生命周期钩子函数,将根组件的_route
响应式化(后面会用到)。
接下来就是路由的初始化,通过将配置项进行解析,执行以下流程。
上面的流程中,要注意以下几点:
- Matcher进行初始化的时候,会将路由表制作成路由映射,后面调用
router
的切换路由的方法的时候,会从这里拿到相应的路由配置。 - History进行初始化的时候,会进行根据不同的类型路由来进行不同的事件的注册,如果是hash或者h5类型的话,会在浏览器上注册事件。回调事件也是进行更新路由视图的起点。
- 在
beforeCreate
的生命周期钩子函数中,只在路由对应的根组件中提添加_route
响应式属性,在其他的组件没有,只能进行代理访问。 - 在
router-view
组件中进行注册_route
回调的前提是在render
函数中引用到,这个要得了解什么响应数据的依赖的添加的时机才能够明白。
更新路由
首先要明白路由更新的起点在哪?根据不同类型的路由分类如下:
路由类型 | 更新起点 |
---|---|
Hash | popState、pushState、hashChange、replaceState、go、push、replace |
H5 | popState、pushState、replaceState、go、push、replace |
Abstract | go、push、replace |
具体的流程图如下:
Vuex
和Vue Router
很骚的一点就是它会去适当使用Vue
的数据响应系统来进行事件通知功能,这是很巧妙的设计(不用关心Vue的版本,是使用Object.defineProperty
还是代理来实现的),同时也是其强耦合度的体现。
入口
在install.js
文件中,对路由、路由组件、路由混入事件、路由响应式对象创建的操作等进行了执行。下面会进行解释作用。
function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
// 进行注册router实例
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
// 在data之后进行初始化
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
beforeCreate () {
// 在beforeCreate执行环境的时候,this指向的是新创建出来的vm实例
if (isDef(this.$options.router)) {
// 如果配置项有router选项的时候,那么这个vm实例就是router的根组件
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
// 定义响应数据。在router-view组件(后面会说)中的渲染函数中会访问到这个属性,同时会添加上依赖。当修改到本数据的时候,会触发数据相应系统,重新渲染对应的router-view。更改视图层
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
// 如果不是路由根目录组件的时候,那么就会将_routerRoot属性赋值为根目录组件
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
// 进行注册路由操作
registerInstance(this, this)
},
destroyed () {
// 进行移除操作
registerInstance(this)
}
})
// 下面两个方法都是代理操作
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)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
在安装文件干了三件事:
-
混入钩子函数,进行路由注册,并且进行定义响应式数据,方便后面路由改变的时候通知视图层进行更新
-
进行代理操作,实例访问
$router
或者$route
属性的时候会代理到跟组件的_route
属性中(所以其实在对$route
进行观察的时候,实际上是对根目录的_route
属性进行观察,而这个属性已经变成了响应型数据,所以路由改变的时候能够实现回调观察的作用)一张图来说明引用的整个流程: -
注册全局组件。
Router的组成
1.VueRouter实例
VueRouter
类是对外的接口,用户通过创建实例来进行路由控制,VueRouter
类是对路由配置以及钩子函数进行管理、对内部功能进行代理的类,并不直接参与路由跳转时候的具体实现(仅仅是代理),而是交给其内部工具来完成:内部有