前言
上一篇我们聊了:Vue-Router源码学习之install方法
虽然最近需求着实不少,但是感觉自己学习劲头还是蛮足的,并没有被需求压垮。今天,带来Vue-Router源码解析系列的第二篇文章:index.js。
正文
vue-router类里面都做了什么?
index.js是vue-router这个类的主构造函数,所以内容上算是比较关键的:
从图片中我们可以看出来,这是一个ES6声明类的方法,vue-router源码中类的声明都是使用类ES的语法,constructor (options: RouterOptions = {}),在vue-router中使用了flow.js做了类型的检查,什么是flow.js?flow.js怎么使用呢?因为篇幅原因,这里就暂时先不做涉及。各位小伙伴,可以参看官网:https://flow.org/en/docs/types/
解析:constructor
首先我们来看一下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模式 h5的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最核心的内容之一:
解析:mode
我们知到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
下面说一下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足够判断是哪种模式。
到现在,我们上一章的init函数已经串联起来了,不知道大家感觉怎么样?我感觉舒服了很多了。
路由守卫
用过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函数后,可以得到注册函数的清除函数,清除的是钩子数组中对应的函数,还有这么一手,牛的一匹。(让代码教你如何熟练使用闭包~)
vue-router的编程式导航是怎么做的?
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方法内的对应方法。
总结完毕 我们学到了什么? history真重要,我要好好看看他内部的实现
真的没出息的总结。
vue-router类中还有一部分对options.routes的处理
options.routes 就是 [{path : 'xxx',components : 'xxx'}] 这个数组
生成一个根据options.routes的一份比对程序,完成程序的比对。
下一期的内容要在继续学习index.js和开荒history中进行一个抉择,具体是什么内容大家可以积极留言,给个方向呀。
结束语
每一个前端er(boy and girl) 你们都不是一个人在战斗。
安排!!!!