VueRouter 的实现是⼀个类,首先看看 constructor 构造函数。

其中 this.app 表示根 Vue 实例, this.apps 保存所有⼦组件的Vue 实例, this.options 保存传⼊的路由配置, this.beforeHooks 、this.resolveHooks 、 this.afterHooks 表示⼀些钩子函数, this.matcher表⽰路由匹配器, this.fallback 表⽰路由创建失败的回调函数, this.mode表示路由创建的模式, this.history 表⽰路由历史的具体的实现实例,它是根据 this.mode 的不同实现不同,它有 History 基类,然后不同的 history 实现都是继承 History 。
当我们 new Vue{ router : new VueRouter({
routes: []
})
})
候会把 router 作为配置的属性传⼊,当我们在 beforeCreated 混入的时候

所以每个组件在执⾏ beforeCreated 钩⼦函数的时候,都会执⾏ router.init ⽅法

init 的逻辑很简单,它传⼊的参数是 Vue 实例,然后存储到 this.apps 中;只有根 Vue 实例会保存到 this.app 中,并且会拿到当前的 this.history ,根据它的不同类型来执⾏不同逻辑,由于我们平时使⽤ hash 路由多⼀些,所以我们先看这部分逻辑,先定义了 setupHashListener函数,接着执⾏了 history.transitionTo ⽅法。


matcher
在 VueRouter 的 constructor 中 this.matcher = createMatcher(options.routes || [], this)。
matcher 相关的实现都在 src/create-matcher.js 中,
createMatcher 接受两个参数, ⼀个是 router ,它是我们 new VueRouter 返回的实例,⼀个是routes ,它是⽤户定义的路由配置,createMatcher 返回了 match 和 addRoutes 两个方法

createRouteMap 函数的⽬标是把⽤户的路由配置转换成⼀张路由映射表,它包含 3 个部分, pathList 存储所有的 path , pathMap 表⽰⼀个 path 到 RouteRecord 的映射关系,⽽nameMap 表示 name 到 RouteRecord 的映射关系。
看下 RouteRecord 的 定义


RouteRecord 的创建是通过遍历 routes 为每⼀个 route 执⾏ addRouteRecord ⽅法⽣成⼀条记录。
RouteRecord 中 path 是规范化后的路径,它会根据 parent 的 path 做计算; regex 是⼀个正则表达式的扩展,它利⽤了 path-to-regexp 这个⼯具库,把 path 解析成⼀个正则表达式的扩展;components 是⼀个对象,通常我们在配置中写的 component 实际上这⾥会被转换成{components: route.component} ; instances 表⽰组件的实例,也是⼀个对象类型; parent
表示父的 RouteRecord ,因为我们配置的时候有时候会配置⼦路由,所以整个 RouteRecord 也就是⼀个树型结构。
如果配置了 children ,那么递归执⾏ addRouteRecord ⽅法,并把当前的 record 作为parent 传入,通过这样的深度遍历,我们就可以拿到⼀个 route 下的完整记录。
然后就是 给 pathMap,pathList, 或者 nameMap 添加记录

由于 pathList 、 pathMap 、 nameMap 都是引⽤类型,所以在遍历整个 routes 过程中去执⾏addRouteRecord ⽅法,会不断给他们添加数据。那么经过整个 createRouteMap ⽅法的执⾏,我们得到的就是 pathList 、 pathMap 和 nameMap 。其中 pathList 是为了记录路由配置中的所有path ,而 pathMap 和 nameMap 都是为了通过 path 和 name 能快速查到对应的RouteRecord 。
addRoutes
addRoutes ⽅法的作⽤是动态添加路由配置,在实际开发中有些场景是需要根据⼀些条件动态添加路由,所以 Vue-Router 也提供了这⼀接⼝

addRoutes 的⽅法⼗分简单,再次调⽤ createRouteMap 即可,传⼊新的 routes 配置,由于pathList 、 pathMap 、 nameMap 都是引⽤类型,执⾏ addRoutes 后会修改它们的值。
回到 createMatcher 的 match 方法
match ⽅法接收 3 个参数,其中 raw 是 RawLocation 类型,它可以是⼀个 url 字符串,也可以是⼀个 Location 对象; currentRoute 是 Route 类型,它表⽰当前的路径; redirectedFrom 和重定向相关,这⾥先忽略。 match ⽅法返回的是⼀个路径,它的作⽤是根据传⼊的 raw 和当前的路径 currentRoute 计算出⼀个新的路径并返回。


normalizeLocation ⽅法的作⽤是根据 raw , current 计算出新的 location 。计算出新的 location 后,对 location 的 name 和 path 的两种情况做了处理。
name: 有 name 的情况下就根据 nameMap 匹配到 record ,它就是⼀个 RouterRecord 对象,如果record 不存在,则匹配失败,返回⼀个空路径;然后拿到 record 对应的 paramNames ,再对⽐currentRoute 中的 params ,把交集部分的 params 添加到 location 中,然后在通过fillParams ⽅法根据 record.path 和 location.path 计算出 location.path ,最后调⽤_createRoute(record, location, redirectedFrom) 去⽣成⼀条新路径,该⽅法我们之后会介绍。
path:通过 name 我们可以很快的找到 record ,但是通过 path 并不能,因为我们计算后的location.path 是⼀个真实路径,⽽ record 中的 path 可能会有 param ,因此需要对所有的pathList 做顺序遍历, 然后通过 matchRoute 方法根据record.regex 、 location.path 、 location.params 匹配,如果匹配到则也通过_createRoute(record, location, redirectedFrom) 去⽣成⼀条新路径。因为是顺序遍历,所以我们书写路由配置要注意路径的顺序,因为写在前⾯的会优先尝试匹配。
然后调用 _createRoute, _createRoute 会调用 createRoute, createRoute 可以根据 record 和 location 创建出来,最终返回的是⼀条 Route 路径,。在 Vue-Router 中,所有的 Route 最终都会通过 createRoute 函数创建,并且它最后是不可以被外部修改的。
Route 对象中有⼀个⾮常重要属性是 matched ,它通过formatMatch(record) 计算⽽来:

可以看它是通过 record 循环向上找 parent ,只到找到最外层,并把所有的 record 都 push 到⼀个数组中,最终返回的就是 record 的数组,它记录了⼀条线路上的所有 record 。 matched属性⾮常有⽤,它为之后渲染组件提供了依据。
总结
createMatcher: 根据路由的配置描述建立映射表,包括路径、名称到路由 record 的映射关系, 最重要的是 createRouteMap 这个方法,这里也是动态路由匹配和嵌套路由的原理。
addRoutes: 动态添加路由配置
match: 根据传入的 raw 和当前的路径 currentRoute 计算出一个新的路径并返回。
网友评论