vue-router 源码分析——2. router-link 组件是如何实现导航的
小伙伴们晚上好,今天继续发vue-router的源码分析。
另外今天我所在的小区超市被零元购了哈哈,超市老板欠债,债权人来超市搬货,最终JC介入。
可见现在的经济有多差。
官网例子
-
在官网例子中,对做了三个注释:这是个组件、传入to属性,渲染成标签。
-
以我们主要分析 router-link 组件如果利用必要的数据来实现导航的。
-
这里先解释一下,渲染html页面的功能,是vue-router调用了vue的render函数,这个是vue的核心功能不做分析。所以这里是分析router是如何定位到对应路由以及做了哪些信息收集和处理的。
<script src="https://unpkg.com/vue@2/dist/vue.js"></script> <script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script> <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> </div>
Vue实例挂载link组件,从而可以使用标签
-
这里的install.js实际上是一个VUE的插件,这样当创建和挂载根实例时自动导入了插件。
-
这里面还涉及到VUE的混入(Vue.mixin),实现了在任意组件内通过this.$ router和this.$route来访问路由器和当前路由。感兴趣的小伙伴可以去看VUE官网的解释。
// ./install.js import Link from './components/link' export function install(Vue) { Vue.component('RouterLink', Link) }
link.js源码内容
-
传入了一个to变量,对应'/foo',tag默认为'a',即标签.
-
render函数是vue用来描述如果构建虚拟DOM的,即如何按某个定义来构建组件。
-
如何渲染为html是vue中的核心内容,这里不做讨论,只分析vue-router中的源码内容,即resolve方法。
const { location, route, href } = router.resolve( this.to, current, this.append ) ... const classes = {} const data: any = { class: classes } ... return h(this.tag, data, this.$slots.default) } }
路由实例的 resolve 方法
-
resolve入参的to是我们传入的字符串'/foo',current是当前路由this.$route,append是undefine。
-
resolve 方法内部又调用了很多函数和方法,得到需要的数据并返回。
-
让我们接着分析调用的函数(normalizeLocation,this.match, createHref)。
// ./router.js export default class VueRouter { ... resolve ( to: RawLocation, current?: Route, append?: boolean ) { ... const location = normalizeLocation(to, current, append, this) const route = this.match(location, current) const fullPath = route.redirectedFrom || route.fullPath const base = this.history.base const href = createHref(base, fullPath, this.mode) // this.mode 看做 'hash' 即可 return { location, route, href ... } } }
normalizeLocation 函数分析
-
入参说明:raw = ‘/foo', current = this.route, append = undifune, router = this.$router
-
parsePath函数暂时没有额外影响,这里不做分析,只是说明 parsePath 的具体内容
-
最终返回一个对象,里面有 _normalized: true 和 path: '/foo'相关内容。
// ./util/location.js export function normalizeLocation( raw: RawLocation, current: ?Route, append: ?boolean, router: ?VueRouter ): Location { let next: Location = typeof raw === 'string' ? { path: raw } : raw if (next._normalized) { return next } ... // parsePath 的实际内容为 {'path': '/foo', 'query': '', 'hash': ''} const parsedPath = parsePath(next.path || '') // basePath 为当前的路由的路径 const basePath = (current && current.path) || '/' // resolvePath函数对'/foo'也没有额外影响,可以理解直接返回了'/foo'赋值给path const path = parsedPath.path ? resolvePath(parsedPath.path, basePath, append || next.append) : basePath ... return { _normalized: true, path, ... } }
this.match方法获得的route是什么
-
对应代码 const route = this.match(location, current),current = this.$route。
-
match方法和resolve方法一样,是定义在VueRouter中的,它直接返回了路由匹配器的match方法。
-
路由匹配器中的match方法会遍历pathList和pathMap,利用正则表达式查看对应的path是否匹配,如果匹配,这里则返回 _createRoute 函数调用。
-
-
pathList和pathMap是在初始化router时生成的,这里为方便理解,再说明一下。
-
-
-
pathList是一个数组,记录着我们初始化时定义的各个url path,例如'/foo','/bar'
-
-
-
pathMap是一个哈希结构,key为path,value为相关的数据(比如path, component, regex等等)。
-
-
_createRoute 函数又调用了 ./util/route.js 的 createRoute 函数。它返回了一个被冻结的对象,里面主要有path和matched属性,matched在这里可以看做等于 [record]。
-
所以this,match方法返回的是一个和我们输入的路径to匹配的一个route对象,里面有我们需要的path,record等内容。
// ./router.js export default class VueRouter { ... match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route { return this.matcher.match(raw, current, redirectedFrom) } } // ./create-matcher.js export function createMatcher(...) { ... function match { raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location }: Route { // 由于传入的raw是已经标准化过的,所以这里的location和raw没有任何区别 const location = normalizedLocation(raw, currentRoute, false, router) const { name } = location if (name) { ... } else if (location.path) { location.params = {} for (let i = 0; i < pathList.length; i++) { const path = pathList[i] const record = pathMap[path] if (matchRoute(record.regex, location.path, location.param)) { return _createRoute(record, location, redirectedFrom) } } } } function matchRoute( regex: RouteRegExp, path: string, params: Object ): boolean { const m = path.match(regex) if (!m) { return false } else if (!params) { return true } ... return true } function _createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { ... return createRoute(record, location. redirectedFrom, router) } } // ./util/route.js export function createRoute( record: ?RouteRecord, location: Location, redirectedFrom?: ?Location, router?: VueRouter ): Route { const route: Route = { path: location.path || '/', matched: record ? formatMatch(record) :[], // 这里可以先简单理解为 [record] ... } return Object.freeze(route) }
createHref函数分析
-
对应代码 href = createHref(base, fullPath, this.mode),对应base = undefine, fullPath = '/foo', this.mode = 'hash'。
-
在vue-route中,默认为hash模式,所以所有的的path前面都会带一个#号
-
这个#号就是在这个函数中体现的
// ./router.js function createHref(base: string, fullPath: string, mode) { var path = mode === 'hash' ? '#' + fullPath : fullPath return base ? cleanPath(base + '/' + path) : path }
总结
-
recolve返回的对象里面的内容主要为location, route , href。
-
location是标准化后的to,并且打上了标记表示已标准化,防止多次标准化,提升效率。
-
route是通过遍历pathList和pathMap,利用正则表达式找到的和to匹配的路由对象,里面包含很多需要的内容。
-
href在默认的hash模式下,会在to的前面加上#号,例如这里的'#/foo'。
// ./router.js export default class VueRouter { ... resolve(to, current, append) { const location = normalizeLocation(to, current, append, this) const route = this.match(location, current) const fullPath = route.redirectedFrom || route.fullPath const base = this.history.base const href = createHref(base, fullPath, this.mode) return { location, route, href, ... } } }
- 后续的内容就是vue利用h函数将link组件渲染为html的内容,例如设置类名,定义handler函数处理跳转,绑定点击事件,指定 a 标签等等。
