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
runQueue(queue,iterator,()=>{// enter 后的回调函数们 用于组件实例化后需要执行的一些回调constpostEnterCbs=[]// leave 完了后 就要进入 enter 阶段了constenterGuards=extractEnterGuards(activated,postEnterCbs,()=>{returnthis.current===route})// enter 的回调钩子们依旧有可能是异步的 不仅仅是异步组件场景runQueue(enterGuards,iterator,()=>{// ...})})
在上篇中介绍了 vue-router 的整体流程,但是具体的
history
部分没有具体分析,本文就具体分析下和history
相关的细节。初始化 Router
通过整体流程可以知道在路由实例化的时候会根据当前
mode
模式来选择实例化对应的History
类,这里再来回顾下,在src/index.js
中:可以看到 vue-router 提供了三种模式:
hash
(默认)、history
以及abstract
模式,还不了解具体区别的可以在文档 中查看,有很详细的解释。下面就这三种模式初始化一一来进行分析。HashHistory
首先就看默认的
hash
模式,也应该是用的最多的模式,对应的源码在src/history/hash.js
中:可以看到在实例化过程中主要做两件事情:针对于不支持 history api 的降级处理,以及保证默认进入的时候对应的 hash 值是以
/
开头的,如果不是则替换。值得注意的是这里并没有监听hashchange
事件来响应对应的逻辑,这部分逻辑在上篇的router.init
中包含的,主要是为了解决 vuejs/vue-router#725,在对应的回调中则调用了onHashChange
方法,后边具体分析。友善高级的 HTML5History
HTML5History
则是利用 history.pushState/repaceState API 来完成 URL 跳转而无须重新加载页面,页面地址和正常地址无异;源码在src/history/html5.js
中:可以看到在这种模式下,初始化作的工作相比 hash 模式少了很多,只是调用基类构造函数以及初始化监听事件,不需要再做额外的工作。
AbstractHistory
理论上来说这种模式是用于 Node.js 环境的,一般场景也就是在做测试的时候。但是在实际项目中其实还可以使用的,利用这种特性还是可以很方便的做很多事情的。由于它和浏览器无关,所以代码上来说也是最简单的,在
src/history/abstract.js
中:可以看出在抽象模式下,所做的仅仅是用一个数组当做栈来模拟浏览器历史记录,拿一个变量来标示当前处于哪个位置。
三种模式的初始化的部分已经完成了,但是这只是刚刚开始,继续往后看。
history 改变
history 改变可以有两种,一种是用户点击链接元素,一种是更新浏览器本身的前进后退导航来更新。
先来说浏览器导航发生变化的时候会触发对应的事件:对于 hash 模式而言触发
window
的hashchange
事件,对于 history 模式而言则触发window
的popstate
事件。先说 hash 模式,当触发改变的时候会调用
HashHistory
实例的onHashChange
:对于 history 模式则是:
上边的
transitionTo
以及replaceHash
、getLocation
、handleScroll
后边统一分析。再看用户点击链接交互,即点击了
<router-link>
,回顾下这个组件在渲染的时候做的事情:这里一个关键就是绑定了元素的
click
事件,当用户触发后,会调用router
的push
或replace
方法来更新路由。下边就来看看这两个方法定义,在src/index.js
中:可以看到其实他们只是代理而已,真正做事情的还是
history
来做,下面就分别把 history 的三种模式下的这两个方法进行分析。HashHistory
直接看代码:
操作是类似的,主要就是调用基类的
transitionTo
方法来过渡这次历史的变化,在完成后更新当前浏览器的 hash 值。上篇中大概分析了transitionTo
方法,但是一些细节并没细说,这里来看下遗漏的细节:这里有一个很关键的路由对象的
matched
实例,从上次的分析中可以知道它就是匹配到的路由记录的合集;这里从执行顺序上来看有这些resolveQueue
、extractLeaveGuards
、resolveAsyncComponents
、runQueue
关键方法。首先来看
resolveQueue
:可以看出
resolveQueue
就是交叉比对当前路由的路由记录和现在的这个路由的路由记录来决定调用哪些路由记录的钩子函数。继续来看
extractLeaveGuards
:可以看到在执行
extractLeaveGuards
的时候首先需要调用flatMapComponents
函数,下面来看看这个函数具体定义:此时需要仔细看下调用
flatMapComponents
时传入的fn
:先来看
extractGuard
的定义:很简答就是取得组件定义时的
key
配置项的值。再来看看具体的
wrapLeaveGuard
是干啥用的:其实这个函数还可以这样写:
这样整个的
extractLeaveGuards
就分析完了,这部分还是比较绕的,需要好好理解下。但是目的是明确的就是得到将要离开的组件们按照由深到浅的顺序组合的beforeRouteLeave
钩子函数们。再来看一个关键的函数
resolveAsyncComponents
,一看名字就知道这个是用来解决异步组件问题的:下面继续看,最后一个关键的
runQueue
函数,它的定义在src/util/async.js
中:可以看出就是一个执行一个函数队列中的每一项,但是考虑了异步场景,只有上一个队列中的项显式调用回调的时候才会继续调用队列的下一个函数。
在切换路由过程中调用的逻辑是这样的:
而
queue
是上边定义的一个切换周期的各种钩子函数以及处理异步组件的“异步”钩子函数所组成队列,在执行完后就会调用队列执行完成后毁掉函数,下面来看这个函数做的事情:仔细看看这个
extractEnterGuards
,从调用参数上来看还是和之前的extractLeaveGuards
是不同的:可以看出此时整体的思路还是和
extractLeaveGuards
的差不多的,只是多了cbs
回调数组 和isValid
校验函数,截止到现在还不知道他们的具体作用,继续往下看此时调用的runQueue
:可以看到此时执行
enterGuards
队列的迭代函数依旧是上边定义的iterator
,在迭代过程中就会调用wrapEnterGuard
返回的routeEnterGuard
函数:这个
poll
又是做什么事情呢?isValid
的定义就是很简单了,通过在调用extractEnterGuards
的时候传入的:回到执行
enter
进入时的钩子函数队列的地方,在执行完所有队列中函数后会调用传入runQueue
的回调:通过上篇分析可以知道
confirmTransition
的回调做的事情:针对于
HashHistory
来说,调用transitionTo
的回调就是:其实就是更新浏览器的 hash 值,
push
和replace
的场景下都是一个效果。回到
confirmTransition
的回调,最后还做了一件事情ensureURL
:此时
push
为undefined
,所以调用replaceHash
更新浏览器 hash 值。HTML5History
整个的流程和
HashHistory
是类似的,不同的只是一些具体的逻辑处理以及特性,所以这里呢就直接来看整个的HTML5History
:这样可以看出和
HashHistory
中不同的是这里增加了滚动位置特性以及当历史发生变化时改变浏览器地址的行为是不一样的,这里使用了新的 history api 来更新。AbstractHistory
抽象模式是属于最简单的处理了,因为不涉及和浏览器地址相关记录关联在一起;整体流程依旧和
HashHistory
是一样的,只是这里通过数组来模拟浏览器历史记录堆栈信息。小结
整个的和 history 相关的代码到这里已经分析完毕了,虽然有三种模式,但是整体执行过程还是一样的,唯一差异的就是在处理location更新时的具体逻辑不同。
欢迎拍砖。
The text was updated successfully, but these errors were encountered: