一.论断后行
导航守卫其实就是将导航守卫的钩子函数放到一个数组queue中,而后通过runQueue一个一个执行,并且只有手动调用next()之后才会跳到下一个函数执行。
二、流程
1.VueRouter
//【代码块1】
//代码所在目录:src/index.js
export default class VueRouter {
//...
//matcher章节讲过createMatcher
this.matcher = createMatcher(options.routes || [], this)
//...
//match章节讲过
match (
raw: RawLocation,
current?: Route,
redirectedFrom?: Location
): Route {
return this.matcher.match(raw, current, redirectedFrom)
}
//...
init(app){
//...
if(history instanceof HTML5History){
//门路切换的一个入口,另一个入口是push()/replace()[this.history.push()/replace()]
// transitionTo见【代码块2】
history.transitionTo(history.getCurrentLocation())
}
}
}
2.History对象中的整体流程
//【代码块2】
//代码所在目录:src/history/base.js
export class Hisroty{
//..
current: Route;
//...
//在路由初始化的时候就会执行一次history.transitionTo()
//同时路由切换的另一个入口push()/replace()办法外面也会执行transitionTo
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
//location为新的跳转地址,比方: '/add'
//onComplete是跳转胜利的回调
//onAbort是跳转失败的回调
//this.router.match是调用的matcher的match办法,上一章讲过,match办法在src/index.js中class VueRouter中定义
//match办法返回一个route对象,route对象的属性比拟丰盛,比方:
//fullPath、hash、matched(是一个对象,外面又蕴含了path、regex等属性)、meta、name、params、path、query等
//这里传入的参数是location(要跳转的地址)、this.currrent(以后的门路)计算出一个新的门路
//计算方法见match办法,大抵如下
//1.将location通过nomalizeLocation变为、一个有hash、path、query、_normalized等属性的对象
//2.通过这个新对象去后面生成的路由映射表中匹配对应的路由对象record
//3.在将record略微做一些解决就失去了route,并利用freeze解冻之后返回
const route = this.router.match(location, this.current)
//comfirmTransition()办法实现一次真正的门路切换(见下文)
this.confirmTransition(route, () => {
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
// fire ready cbs once
if (!this.ready) {
this.ready = true
this.readyCbs.forEach(cb => { cb(route) })
}
}, err => {
if (onAbort) {
onAbort(err)
}
if (err && !this.ready) {
this.ready = true
this.readyErrorCbs.forEach(cb => { cb(err) })
}
})
}
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
//this.current是History对象须要保护的一个以后的路由对象,即不论咱们切换多少次门路,在路劲切换胜利后,都会扭转以后的current
const current = this.current
//勾销这次门路跳转的办法
const abort = err => {
if (isError(err)) {
if (this.errorCbs.length) {
this.errorCbs.forEach(cb => { cb(err) })
} else {
warn(false, 'uncaught error during route navigation:')
console.error(err)
}
}
onAbort && onAbort(err)
}
if (
//isSameRoute判断我以后的门路和我要跳转的门路是不是同一个门路(详情能够见【代码块3】)
isSameRoute(route, current) &&
// in the case the route map has been dynamically appended to
route.matched.length === current.matched.length
) {
//如果为像她的地址,就不须要执行操作,间接勾销跳转就行
//ensureURL前面讲,与URL无关
this.ensureURL()
return abort()
}
//导航守卫相干逻辑
const {
updated,
deactivated,
activated
} = resolveQueue(this.current.matched, route.matched)
//resolveQueue的参数值得看一下
//首先this.current和route咱们后面曾经晓得他们是Route类型的数据,而Route类型是由一个matched的属性的
//详情见【代码块4】
//后果:matched是一个存储了以后门路到根门路的所有门路的数组(通过一直的变量.parent)(程序为先父后子)
//resolveQueue见【代码块5】
//后果:
//updated保留的是新老门路雷同的局部
//activated保留的是新门路新增的局部
//deactivated保留的是老门路删除的局部
//举例 /foo/bar==>/foo updated: /foo的record activated: 空 deactivated: /bar的record
// /foo==>/foo/bar updated: /foo的record activated: /bar的record deactivated: 空
//queue是一个NavigationGuard类型的数组
//NavigationGuard类型见【代码块6】
//后果,NavigationGuard是一个函数,有三个参数:to、from、next(next也是一个函数),to和from都是Route类型
//利用[].concat将上面这个函数或者异步钩子拍平到一个一维数组中,即queue中
const queue: Array<?NavigationGuard> = [].concat(
// #残缺的导航解析流程
// 导航被触发。
// 在失活的组件里调用 beforeRouteLeave 守卫。
// 调用全局的 beforeEach 守卫。
// 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
// 在路由配置里调用 beforeEnter。
// 解析异步路由组件。
// 在被激活的组件里调用 beforeRouteEnter。
// 调用全局的 beforeResolve 守卫 (2.5+)。
// 导航被确认。
// 调用全局的 afterEach 钩子。
// 触发 DOM 更新。
// 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入。
//这外面的具体过程太绕了,先跳过,当前有精力再认真看
extractLeaveGuards(deactivated), //在失活组件中调用来到守卫 拿到组件定义中beforeRouteLeave的定义
// global before hooks
this.router.beforeHooks, //调用全局的beforeEach守卫 调用beforeEach的时候会往beforeHooks中增加一个
// in-component update hooks
extractUpdateHooks(updated), //在重用的组件里调用beforeRouteUpdate守卫 拿到组件定义中beforeRouteUpdate的定义
// in-config enter guards
activated.map(m => m.beforeEnter), //在路由配置里调用 beforeEnter
// async components
resolveAsyncComponents(activated) //解析异步路由组件
)
this.pending = route
//定义了一个迭代器函数
const iterator = (hook: NavigationGuard, next) => {
if (this.pending !== route) {
return abort()
}
try {
//hook是NavigationGuard类型的一个函数,NavigationGuard类型后面提到过有to、from、next三个参数
//所以route对应to,current对应from, to对应next函数
//有runQueue调用中能够看出hook为queue中的一个函数,在导航守卫中即导航守卫相干的钩子函数,
//而这些构造函数正好也对应了to、from和next三个参数。
//当to为false的时候,就会勾销路由跳转,当false为字符串、对象之类的时候,跳转路由,并勾销,否则执行next(to),
//也就是执行咱们在钩子函数中的next中写的货色,在外面咱们会手动调用一个next,这个next的执行就是iterator的第二个参数next,
//也就是对应了async.js中runQueue中执行的step+1,即跳到下一个函数执行
//所以如果咱们不手动调用next,就不会执行队列中的下一个函数,程序就会卡住
hook(route, current, (to: any) => {
if (to === false || isError(to)) {
// next(false) -> abort navigation, ensure current URL
this.ensureURL(true)
abort(to)
} else if (
typeof to === 'string' ||
(typeof to === 'object' && (
typeof to.path === 'string' ||
typeof to.name === 'string'
))
) {
// next('/') or next({ path: '/' }) -> redirect
abort()
if (typeof to === 'object' && to.replace) {
this.replace(to)
} else {
this.push(to)
}
} else {
// confirm transition and pass on the value
next(to)
}
})
} catch (e) {
abort(e)
}
}
//runQueue执行下面的queue
//runQueue定义见【代码块7】
//论断:queue外面其实是一些导航守卫相干的钩子函数,而runQueue就会以队列的模式
//一个一个的执行queue外面的函数(具体执行形式是执行下面的迭代)
//当一个函数执行完之后,并在回调函数外面手动调用next()才会跳到下一个函数执行。
//也就是跳到下一个函数的掌控权在本人手中,不论在回调中同步还是异步的调用next,总之只有当手动调用了next()才会执行下一个函数
runQueue(queue, iterator, () => {
const postEnterCbs = []
const isValid = () => this.current === route
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
return abort()
}
this.pending = null
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
postEnterCbs.forEach(cb => { cb() })
})
}
})
})
}
updateRoute (route: Route) {
const prev = this.current
this.current = route
this.cb && this.cb(route)
this.router.afterHooks.forEach(hook => {
hook && hook(route, prev)
})
}
}
3.判断新旧门路是否雷同
//【代码块3】
//代码所在目录:src/util/route.js
export function isSameRoute (a: Route, b: ?Route): boolean {
if (b === START) {
return a === b
} else if (!b) {
return false
} else if (a.path && b.path) {
return (
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
a.hash === b.hash &&
isObjectEqual(a.query, b.query)
)
} else if (a.name && b.name) {
return (
a.name === b.name &&
a.hash === b.hash &&
isObjectEqual(a.query, b.query) &&
isObjectEqual(a.params, b.params)
)
} else {
return false
}
}
4.Route对象的定义及route对象的matched属性
//【代码块4】
//代码所在目录:src/util/route.js
const route: Route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || '/',
hash: location.hash || '',
query,
params: location.params || {},
fullPath: getFullPath(location, stringifyQuery),
matched: record ? formatMatch(record) : []
//matched属性:咱们在执行createRoute的时候会传入一个匹配到的路由对象record,而后通过record计算出matched
//具体计算方法见formatMatch办法(下文)
//后果:matched是一个存储了以后门路到根门路的所有门路的数组(通过一直的变量.parent)(程序为先父后子)
}
//farmatMatch办法即通过以后的record向上遍历它的parent,把所有的parent遍历进去放入到一个数组中并返回
//留神这个数组中的程序的先父后子(unshift)
//即记录到以后门路到根门路的所有门路
function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
const res = []
while (record) {
res.unshift(record)
record = record.parent
}
return res
}
5.通过新旧门路找到不同
//【代码块5】
//代码所在目录:src/history/base.js
function resolveQueue (
current: Array<RouteRecord>,
next: Array<RouteRecord>
): {
updated: Array<RouteRecord>,
activated: Array<RouteRecord>,
deactivated: Array<RouteRecord>
} {
let i
//由后面matched属性的剖析咱们能够晓得current是一个存储了以后门路到其根门路的所有门路的record的数组,程序为先父后子
//next为咱们要去到的地址到其根门路的所有门路的record的数组,属性为先父后子
//举例: /foo ==> /foo/bar
//curent:[/foo的record]
//next:[/foo的record, /bar的record]
//取到两个数组的最长长度,例子中为2
const max = Math.max(current.length, next.length)
//找打第一个不同的中央就跳出,所以这里的i为1就跳出了
for (i = 0; i < max; i++) {
if (current[i] !== next[i]) {
break
}
}
return {
updated: next.slice(0, i), // /foo的record
activated: next.slice(i), // /bar的record
deactivated: current.slice(i) // 空
}
//所以updated保留的是以后门路与要去到的门路之间雷同的局部
//activated保留的是新门路相较于老门路多进去的局部
//deactivated保留的是老门路删除的局部 (比方/foo/bar ==> /foo deactivated就是/bar)
}
6.NavigationGuard类型
//【代码块6】
//代码所在目录:flow/declarations.js
declare type NavigationGuard = (
to: Route,
from: Route,
next: (to?: RawLocation | false | Function | void) => void
) => any
7.runQueue办法
//【代码块7】
//代码所在目录:src/util/async.js
//参数queue是一个由异步函数啊钩子函数啊之类的函数组成的一个一维数组
//下面这些函数也就是导航守卫相干的一些钩子函数
//参数fn是base.js中调用runQueue的地位下面定义的迭代器iterator
//cb是一个回调函数,具体见runQueu调用的中央[base.js]
//runQueue是一个比拟经典的队列的实现,相当于下面这个函数一个一个执行,这一个执行完,再跳到下一个执行
export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
const step = index => {
//index>=queue.length即队列中的所有函数都执行完了,就调用第三个参数这个回调函数
if (index >= queue.length) {
cb()
} else {
if (queue[index]) {
//如果以后索引对应的函数存在,就执行fn(即传入的迭代器),见base.js
fn(queue[index], () => {
step(index + 1)
})
} else {
step(index + 1)
}
}
}
step(0)
}
发表回复