这里的实现方法大致和源码相同,但结构组成和源码有所出入,比如说匹配路由的方法源码是放在History类上,我几乎都放在VueRouter类上了,因为写的时候忘记看结构了。还有一些正则的方法没有像源码一样处理得很好(源码那我实在看不太懂,只能用自己的理解写一下,history模式同理,我还没全写好) 仓库地址
钩子函数是在哪里执行呢
在说明这个问题前,我们要知道VueRouter在初始化的时候,已经将routes遍历,每一个route都在其原有的信息上加入了更多信息(比如route是没有parent这个属性点,也没有路径的正则匹配的),创建成含有这些信息的record对象
- 在初始化一来是完成下面四步骤,第二就是监听hashChange,load,popState事件
- 创建成route对应的record
- 将目前所有存在的路径加入pathList数组
- 将route.name和对应的record创建映射表nameMap
- 将route.path和对应的record创建映射表pathMap
- 当window.location发生变化,或者用户点击了router-link的时候,会触发router.push这个方法
- router.push方法调用了transitionTo,transitionTo方法
- 首先通过match函数把当前location匹配的record得到,然后创建一个currentRoute对象 1.这个currentRoute的matched是一个数组,数组里面按顺序排列着(父在前)符合这个location的route以及他的父级route
- 把目前的toRoute赋值给fromRoute(旧的路由对象),再把currentRoute赋值给toRoute
- 提供了当ConfirmTransitionTo完成/失败后需要执行的onComplete/onAbort方法
- ConfirmTransitionTo方法主要完成以下步骤,然后调用上诉的onComplete/onAbort方法
- 首先比较toRoute和fromRoute的matched里面,如果顺序和地址都一样的话,说明这个route里面的component不需要重新渲染
let maxLength = Math.max(this.fromRoute.matched.length,this.toRoute.matched.length)
var i
for(i =0;i<maxLength;i++){
//比如父子孙,当i=1是,说明toRoute/fromRoute的父是可以复用,属于updatedRoutes
//toRoute的子孙组件是activatedRoutes
//fromRoute的子孙组件是deviatedRoutes
if(this.fromRoute.matched[i]!==this.toRoute.matched[i]){
break
}
}
let activatedRoutes = this.toRoute.matched&&this.toRoute.matched.slice(i) //
let activatedRoutes = this.fromRoute.matched&&this.fromRoute.matched.slice(i)
let updatedRoutes = this.toRoute.matched&&this.toRoute.matched.slice(0,i)
上面获得了activatedRoutes,activatedRoutes,updatedRoutes这三个数组,就可以开始说重点了
实现路由钩子函数里的方法
var cbs = []
//下面这个方法
//就是将除开beforeRouterEnter、beforeResolve和afterEach钩子函数链接成数组,里面的函数逐一执行,执行完后再执行cb
// runQueue(queue,iterator, cb)
// cb这个方法将beforeRouterEnter和beforeResolve钩子函数链接成数组,
//然后一次执行新连接成的数组里面的函数
//再调用transition传过来的onComplete方法,complete方法会依次执行afterHooks和全局数组cbs里面的方法
//这样就将所有的钩子函数全都执行完
let queue = [].concat([
...extractLeaveRoutesFn(transitionRoutes.deviatedRoutes),//在失活的组件里调用离开守卫
...extractUpdateRoutesFn(transitionRoutes.updatedRoutes),
...this.beforeHooks,///调用全局的beforeEach守卫,
...transitionRoutes.activatedRoutes.map(activatedRoute=>activatedRoute.beforeEnter)
.filter(fn=>fn),//调用路由组件里面beforeEnter
resolveAsyncComponents(transitionRoutes.activatedRoutes) //解析异步组件
])
runQueue(queue,iterator, () =>{
//上面的hook执行完了
var queue = extractEnterGuards(transitionRoutes.activatedRoutes,cb).concat(this.resolveHooks)
runQueue(queue,iterator, ()=> {
this.pending = null;
onComplete(this.toRoute);//这里用来pushstate实现跳转
})
})
全局卫视beforeEach,afterEach,beforeResolve
一般调用方式如下
router.beforeEach((to, from, next) => {
//do somthing
next()
})
router.beforeResolve((to, from, next) => {
//do somthing
next()
})
router.afterEach((to, from, next) => {
//do somthing
})
在VueRouter的类的原型上定义了这三个方法,而在VueRouter的构造函数上已经定义好了beforeHooks,resolveHooks,afterHooks数组
//registerHooks方法把fn加到对应的数组里面
VueRouter.prototype.beforeEach = function(fn){
registerHooks(this.beforeHooks,fn)
}
VueRouter.prototype.beforeResolve = function(fn){
registerHooks(this.resolveHooks,fn)
}
VueRouter.prototype.afterEach = function(fn){
registerHooks(this.afterHooks,fn)
}
路由独享的守卫
下面的beforeEnter是route里面的一个属性,也就是说对应的record上面会有beforeEnter方法,
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
组件内的导航钩子
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
},
beforeRouteUpdate (to, from, next) {
},
beforeRouteLeave (to, from, next) {
}
处理组件内的导航钩子比较特殊
- 首先说说beforeRouteLeave(extractUpdateRoutesFn方法是同理的,只是参数是复用的路由updatedRoutes而已)
extractLeaveRoutesFn(transitionRoutes.deviatedRoutes)
function extractLeaveRoutesFn(deviatedRoutes) {
//参数是deviatedRoutes数组
var fns = []
deviatedRoutes&&deviatedRoutes.forEach(deviatedRoute=>{
//获得component对象,component对象里面就有组件内的导航钩子(如果有设置的话)
let components = deviatedRoute.components
for(let [index,item] of Object.entries(components)){
if(typeof item !=='function'){
//如果component不是function的话,需要通过Vue.extend把它转换成VueComponent类
let def = Vue.extend(item);
//获得这个路由里的实例对象,这个在Vue.mixin里面的registerInstance定义了
let instance = deviatedRoute.instances[index]
//获得VueComponent类的beforeRouteLeave函数
var f = def.options['beforeRouteLeave']
if(f){
//把f的this绑定在实例上
fns.push(f.bind(instance))
}
}
}
})
return fns
}
- extractEnterGuards提取组件内的beforeRouteEnter方法
extractEnterGuards(transitionRoutes.activatedRoutes,cb)
var cbs = []
function extractEnterGuards(activatedRoutes, postEnterCbs) {
var fns = []
activatedRoutes&&activatedRoutes.forEach(activatedRoute=>{
let components = activatedRoute.components
for(let [index,item] of Object.entries(components)){
if(typeof item !=='function'){
let def = Vue.extend(item);
let instance = deviatedRoute.instances[index]
var f = def.options['beforeRouteEnter']
if(f){
var f1 = function(to,from,next){
f(to,from,function (cb) {
//这里的cb就是next方法传入的函数,
//放进全局数组cb里面,onComplete方法需要依次执行里面的方法
if(typeof cb === "function"){
cbs.push(cb)
}
})
next()
}
fns.push(f1)
}
}
}
})
return fns
}