You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
在看 HashHistory 和 HTML5History 的实现时,涉及到父类 History 与其 transitionTo 方法。
在路由发生跳转的时候,需要调用 transitionTo 方法,其中里面便实现了导航守卫。
History
HashHistory 和 HTML5History 都是继承于 History。在调用它们构造函数的时候通过 super 也调用了 History 的构造函数,同时也用到了父类的一些方法,比如 listen 和 transitionTo。
主要来看构造函数 constructor 与 transitionTo 方法的实现。
constructor
History 的构造函数里定义了四个属性。
router
base
current
pending
constructor(router: VueRouter,base: ?string){this.router=routerthis.base=normalizeBase(base)// start with a route object that stands for "nowhere"this.current=STARTthis.pending=null}
router 保存了 Router 实例,方便接下来使用它的属性和方法。
base 是保存则应用的基路径。其中通过 normalizeBase 函数来判断处理,默认为 '/'。
前言
在看 HashHistory 和 HTML5History 的实现时,涉及到父类
History
与其transitionTo
方法。在路由发生跳转的时候,需要调用
transitionTo
方法,其中里面便实现了导航守卫。History
HashHistory 和 HTML5History 都是继承于 History。在调用它们构造函数的时候通过 super 也调用了 History 的构造函数,同时也用到了父类的一些方法,比如 listen 和 transitionTo。
主要来看构造函数
constructor
与transitionTo
方法的实现。constructor
History 的构造函数里定义了四个属性。
router 保存了 Router 实例,方便接下来使用它的属性和方法。
base 是保存则应用的基路径。其中通过
normalizeBase
函数来判断处理,默认为 '/'。current 和 pending 都是用来保存一个 Route 对象。
Route 对象在 flow 里是这样定义的:
一个 Route 对象将包含这样一些属性,一个页面可以用一个 Route 对象来表示。
pending 保存着当前正在进行导航守卫的 Route 对象,current 则保存着完成导航守卫的当前 Route 对象。
current 初始化是一个
START
。START 的定义如下:通过
createRoute
函数创建的START
,是一个除了path
为 '/' 其它属性都为空的 Route 对象。transitionTo
来看下之前的代码里是怎么地使用 transitionTo:
可以看到它接收两个参数,一个是路径,另一个是执行完导航守卫的回调函数。
接下来开始深入了解这个函数。
match 方法先暂时放一放(因为这又是一大块),这里我们只要知道通过它能得到即将调整的路由对象即可。
主要关注的是这两个方法,
confirmTransition
和updateRoute
。updateRoute
我们先从简单的
updateRoute
开始吧,免得到后面忘了它。首先可以猜出它是在confirmTransition
的回调函数里面执行的,说明是在它之后才会执行。updateRoute
主要做了三件事情:listen
监听的 cb,从而进一步更新 router-view 组件。afterEach
钩子。剩下的钩子触发都是发生在
confirmTransition
里面。confirmTransition
confirmTransition
里的代码有点多,且有点复杂。先让我大致说一下它做了哪些实现,再来结合看源代码吧。
resolveQueue
函数找到即将要被销毁的组件,和即将要被激活的组件。queue
中。iterator
,用来在迭代queue
里每一个导航守卫时做些事情。runQueue
开始迭代调用导航守卫,最后执行回调cb
。接下来不会逐句代码解释(添加少量注释),我们的目的是了解上面的大概实现。
实现 1
实现 1 里先是判断要跳转的页面:
isSameRoute
方法判断了两个 Route 对象的 name、hash、query 和 params 属性是否一致,是的话,则表示是同个路由,直接 return,否则就继续往下走。实现 2
实现 2 里做的是找到即将销毁和即将激活的组件:
Route 对象的 matched 属性包括着每一层路由的 RouteRecord 对象(跟 Route 属性差不多,主要多了 beforeEnter 方法)。
resolveQueue
方法通过 matched 属性来来逐个遍历,找到哪一层级开始的路由是不一样的,接下来就从这一层级来更新路由组件。实现 3
实现 3 里收集了组件的导航守卫:
这里需要细读一下
flatMapComponents
函数:flatMapComponents
函数实现的功能是,遍历传入的 matched 里的 components 里的每一个组件(即我们在 new VueRouter 传入的 components),每一个组件都调用一遍 fn 回调函数。extractLeaveGuards
就通过了flatMapComponents
函数来收集即将销毁的组件的 beforeRouteLeave 钩子。而resolveAsyncComponents
也是通过它来收集异步组件。所以实现 3 里将所有即将要执行的钩子(导航守卫)都收集到了
queue
数组里,用来接下来的遍历钩子。实现 4
实现 4 定义一个迭代函数
iterator
,到时每一次触发钩子的时候,会先执行一遍 iterator 函数,并将钩子函数传入。iterator 函数会执行钩子函数,并实现了
next
的使用。next 的使用具体参考 vue-router 全局守卫。实现 5
前面的实现都是为了这里的调用。
这里用到了 runQueue 函数,接收 3 个参数,runQueue 会迭代 queue 数组,每一次都先执行一遍 iterator,iterator 里才是真正地触发钩子。当全部钩子触发完毕,则调用第 3 个参数,即一个回调函数。
queue 里所有钩子触发完后,还需要触发激活组件的 beforeRouteEnter 钩子。
所以需要收集激活组件的 beforeRouteEnter 钩子,再执行一遍 runQueue 函数。以下代码在执行完第一个 runQueue 函数后的回调函数里执行:
extractEnterGuards
专门收集了激活组件的 beforeRouteEnter 钩子。当所有 beforeRouteEnter 钩子执行完毕后,就会调用 confirmTransition 的 cb 了,并将当前 Route 对象作为参数传入。
时光倒流,再回到刚开始调用
confirmTransition
的时候,来瞧瞧当时传了神马作为回调函数。这代码应该很熟悉了,
updateRoute
方法前面已经了解过了,作用是更新 router-view 组件。至此,一套导航守卫的代码就粗略地解读结束,完整的导航解析流程可以参考 官方文档。
心得
导航守卫的源码解读可以说是看 vue-router 源码中最困难的一部分,代码量不少,函数跳来跳去,刚开始确实看得我晕头转向的。
后来多看了几遍,抓住几个重点的函数,其它的暂时放一边,慢慢地也总算是掌握了思路。所以看源码不要指望自己第一遍就能够全部看明白。
以上的解读中会有许多函数没有讲解出来,一方面我觉得没必要太多细致地纠结每一行代码,另一方面函数名基本已经说明其功能。若对它们的实现感兴趣的话,可以打开 vue-router 的源码自己看下。
下期预告
还记得曾经在 Router 调用 constructor 构造函数的时候,我们暂时放一边的
createMatcher
函数吗?在深入了解 transitionTo 的时候,我们就用到了这个
match
方法。到底
createMatcher
做了什么操作?使用了match
方法又有什么特效?下期我们就来深入了解 match 吧。The text was updated successfully, but these errors were encountered: