您好,登录后才能下订单哦!
虽然最近需求着实不少,但是感觉自己学习劲头还是蛮足的,并没有被需求压垮。今天,带来Vue-Router源码解析系列的第二篇文章:index.js。
index.js是vue-router这个类的主构造函数,所以内容上算是比较关键的:
cdn.xitu.io/2018/9/9/165bd0e1aff64406?w=1314&h=1158&f=png&s=225796">
从图片中我们可以看出来,这是一个ES6声明类的方法,vue-router源码中类的声明都是使用类ES的语法,constructor (options: RouterOptions = {})
,在vue-router中使用了flow.js做了类型的检查,
什么是flow.js?flow.js怎么使用呢?因为篇幅原因,这里就暂时先不做涉及。各位小伙伴,可以参看官网:https://flow.org/en/docs/types/
首先我们来看一下constructor内的代码,
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
//默认为hash锚点
let mode = options.mode || 'hash'
//当然使用的是history模式 h6的pushState的方式来实现路由跳转的,对options设置fallback属性为true时会回退到hash模式
// 是否支持回退
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
//没有fallback的话选择锚点模式,node环境选择abstract模式
this.mode = mode
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
我们声明一个vue-router实例的时候是怎么做的?
let router = new Router({
base : '/',
mode : 'history',
routes : [{
component : xxx,
path : xxx
},xxx]
})
constructor (options: RouterOptions = {})
options就是我们刚才上面的一个对象,里面有base、mode、routes等属性
这时候我们知道options是个什么东西了,我们来看看内部对options进行了哪些处理。
首先我们说一下vue-router最核心的内容之一:
我们知到vue-router的路由有两种方式,一种是#锚点性的,第二种是和正常路径一样的,可是vue构建的应用是一个但页面应用如何让他像正常的多页面应用一样,是在不停的改变路径呢?
这里面就使用了html5的history的pushState与replaceState(让页面看起来无刷新的改变路径),具体内容大家可以看一下官网文档和大神张鑫旭的博客(https://www.zhangxinxu.com/wordpress/2013/06/html5-history-api-pushstate-replacestate-ajax/)
在vue-router源码中有一个工具类专门做了这个事情:
我们来看一下vue-router是如何匹配mode的吧:
// vue-router默认使用hash模式
let mode = options.mode || 'hash';
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
// 如果选择了history但是pushState方法并不能使用并且设置了
// 在当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式的情况下。
// (options.fallback默认就是true)
// 如果发现需要回退了,就回到hash锚点模式
if (this.fallback) {
mode = 'hash'
}
// 不在浏览器环境就选择abstract模式(在node环境)
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
// 根据三种情况是成不同的路由转换实例。
// 如果没有mode不是这三种情况就报错。
// HTML5History、HTML5History、HTML5History三个类都是继承与一个base类
// 里面有这三种模式对于路径转换时做的事情进行了一定的封装。
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HTML5History(this, options.base, this.fallback)
break
case 'abstract':
this.history = new HTML5History(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
到这里大家应该对我们写的mode模式有一点的了解了吧。
下面说一下init方法,上一章我们讲了在根节点的beforeCreate生命周期钩子中,使用了init方法,如果忘记了可以翻看上一篇install方法的学习来回归一下
所以app就是根组件,init在执行前要判断一下,vue-router是不是被vue成功use了,因为成功use之后,会把install方法的installed属性设置为true:
init (app: any /* Vue component instance */) {
process.env.NODE_ENV !== 'production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app)
// main app already initialized. (根组件已经被初始化)
if (this.app) {
return
}
this.app = app
const history = this.history
// 当我们的根组件完成了对vue-router的init的时候我们就要完成第一次路由的跳转了
// 当我们的项目启动的时候肯定会有一个路径,这个路径是什么不重要
// 我们在第一次进入这个路径的时候,会进行vue-router的初始化
// 初始化之后要开始展示对应的组件,可是我们vue-router那些popState的事件肯定没有绑定,不会触发啊,
// 怎么办?? 那就手动触发一下这个事情,
// 第一次进入肯定没有对应的事件,不会完成跳转时该做的事情。
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
// 针对设置的不同mode(模式)
// 将mode的时候,每种模式的history实例来源于三个不同的类,所以instanceof足够判断是哪种模式。
用过vue-router的同学们都知道路由守卫的概念,这是在路由跳转的前后等三个地方设置了不同的钩子,帮助我们在进入离开路由前做一些事情。这个钩子是怎么做的呢?
声明了三个数组,存放每个周期内的钩子上绑定的函数。
vue-router全局级别的beforeEach、beforeResolve、afterEach做了什么?
就这么两行代码你敢信??我也很纠结你就干了这么点事情。
执行一下registerHook函数,从字面意思一看就是注册钩子,
function registerHook (list: Array<any>, fn: Function): Function {
list.push(fn)
// 返回值是一个function
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
接收一个生命周期的钩子数组,将我们要执行的函数传到数组内就可以完成注册了,我还没看到这三个数组的内容,但是直觉告诉我很有可能就是,观察者模式(以后就探索去)。注册到这应该就OK了,
为什么还有个返回值呢?
返回值的内容一看就是要清楚钩子内的函数呀,我们调用这个registerHook函数后,可以得到注册函数的清除函数,清除的是钩子数组中对应的函数,还有这么一手,牛的一匹。(让代码教你如何熟练使用闭包~)
push方法与replace、go方法调用对应路由转换实例的对应方法,因为不同模式大家方法肯定都不一样,
back与forward都是go方法传入特殊参数,所以看到这里我们发现history这个实例的内容很关键。
到了这里我们vue-router的主线流程我们已经进行了一个梳理,不知道大家对这一块内容感觉满意吗? 不满意就请不要邮寄刀片哈。
1:mode是设置模式的,有hash、history、abstract三种模式、每个模式会导致vue-router实例的history不同,来自三个不同的类、每个类又继承于总的base类。
2:init方法会初始化整个组件、并且在vue-router的实例中设置了app属性存放根组件(这个确实很有用)、手动的完成初始化后的第一次路由跳转。
3:beforeEach、beforeREsolve、afterEach三个全局的钩子都有对应的钩子函数数组,存放每个周期钩子内要做的事情、使用registerHook方法来完成钩子函数的注册,registerHook也可以清除钩子内对应的函数。
4:push、replace、go等方法都是使用history方法内的对应方法。
真的没出息的总结。
vue-router类中还有一部分对options.routes的处理
options.routes 就是 [{path : 'xxx',components : 'xxx'}] 这个数组
生成一个根据options.routes的一份比对程序,完成程序的比对。
下一期的内容要在继续学习index.js和开荒history中进行一个抉择,具体是什么内容大家可以积极留言,给个方向呀。
每一个前端er(boy and girl) 你们都不是一个人在战斗。
安排!!!!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。