【vue-rouer源码】系列文章
- 【vue-router源码】四、createRouter源码解析
目录
前言
【vue-router源码】电商商城定制开发系列文章将带你从0开始了解vue-router电商商城定制开发的具体实现。电商商城定制开发该系列文章源码参考vue-router v4.0.15。
源码地址:
电商商城定制开发阅读该文章的前提是你最好了解vue-router电商商城定制开发的基本使用,如果你没有使用过的话,可通过学习下。
该篇文章将带你分析createRouter的实现。
使用
const routerHistory = createWebHistory()export const router = createRouter({ history: routerHistory, strict: true, routes: [ { path: '/home', redirect: '/' }, { path: '/', components: { default: Home, other: component }, props: { default: to => ({ waited: to.meta.waitedFor }) }, }, { path: '/nested', alias: '/anidado', component: Nested, name: 'Nested', children: [ { path: 'nested', alias: 'a', name: 'NestedNested', component: Nested, children: [ { name: 'NestedNestedNested', path: 'nested', component: Nested, }, ], }, { path: 'other', alias: 'otherAlias', component: Nested, name: 'NestedOther', }, { path: 'also-as-absolute', alias: '/absolute', name: 'absolute-child', component: Nested, }, ], }, ], async scrollBehavior(to, from, savedPosition) { await scrollWaiter.wait() if (savedPosition) { return savedPosition } else { if (to.matched.every((record, i) => from.matched[i] !== record)) return { left: 0, top: 0 } } return false },})- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
createRouter
在分析createRouter之前,先来看下它的参数类型:
export interface _PathParserOptions { // 使用正则时区分大小写,默认false sensitive?: boolean // 是否禁止尾随斜杠,默认false strict?: boolean // 正则表达式前应该加^,默认true start?: boolean // 正则表达式以$结尾,默认为true end?: boolean}export type PathParserOptions = Pick< _PathParserOptions, 'end' | 'sensitive' | 'strict'>export interface RouterOptions extends PathParserOptions { history: RouterHistory // 路由表 routes: RouteRecordRaw[] // 在页面之间导航时控制滚动行为。可以返回一个 Promise 来延迟滚动。 scrollBehavior?: RouterScrollBehavior // 用于自定义如何解析query parseQuery?: typeof originalParseQuery // 用于自定义查询对象如何转为字符串 stringifyQuery?: typeof originalStringifyQuery // 激活RouterLink的默认类 linkActiveClass?: string // 精准激活RouterLink的默认类 linkExactActiveClass?: string}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
我们来看下createRouter具体做了什么。createRouter方法共885(包含空行)行,乍一看可能会觉得方法很复杂,仔细观察,其实很大一部分代码都是声明一些函数。我们可以先暂时抛开这些函数声明看其余部分。
首先会使用createRouterMatcher方法创建了一个路由匹配器matcher,从options中提取parseQuery、stringifyQuery、history属性,如果options中没有history,抛出错误。
const matcher = createRouterMatcher(options.routes, options)const parseQuery = options.parseQuery || originalParseQueryconst stringifyQuery = options.stringifyQuery || originalStringifyQueryconst routerHistory = options.historyif (__DEV__ && !routerHistory) throw new Error( 'Provide the "history" option when calling "createRouter()":' + ' https://next.router.vuejs.org/api/#history.' )- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
紧接着声明了一些全局守卫相关的变量,和一些关于params的处理方法,其中有关全局守卫的变量都是通过useCallbacks创建的,params相关方法通过applyToParams创建。
// 全局前置守卫相关方法const beforeGuards = useCallbacks<NavigationGuardWithThis<undefined>>()// 全局解析守卫相关方法const beforeResolveGuards = useCallbacks<NavigationGuardWithThis<undefined>>()// 全局后置钩子方法const afterGuards = useCallbacks<NavigationHookAfter>()// 当前路由,浅层响应式对象const currentRoute = shallowRef<RouteLocationNormalizedLoaded>( START_LOCATION_NORMALIZED)let pendingLocation: RouteLocation = START_LOCATION_NORMALIZED// 如果浏览器环境下设置了scrollBehavior,那么需要防止页面自动恢复页面位置// https://developer.mozilla.org/zh-CN/docs/Web/API/History/scrollRestorationif (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) { history.scrollRestoration = 'manual'}// 标准化params,转字符串const normalizeParams = applyToParams.bind( null, paramValue => '' + paramValue)// 编码paramconst encodeParams = applyToParams.bind(null, encodeParam)// 解码paramsconst decodeParams: (params: RouteParams | undefined) => RouteParams = applyToParams.bind(null, decode)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
关于useCallbacks的实现:在useCallbacks中声明一个handlers数组用来保存所有添加的方法,useCallbacks的返回值中包括三个方法:add(添加一个handler,并返回一个删除handler的函数)、list(返回所有handler)、reset(清空所有handler)
export function useCallbacks<T>() { let handlers: T[] = [] function add(handler: T): () => void { handlers.push(handler) return () => { const i = handlers.indexOf(handler) if (i > -1) handlers.splice(i, 1) } } function reset() { handlers = [] } return { add, list: () => handlers, reset, }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
applyToParams的实现:接收一个处理函数和params对象,遍历params对象,并对每一个属性值执行fn并将结果赋给一个新的对象。
export function applyToParams( fn: (v: string | number | null | undefined) => string, params: RouteParamsRaw | undefined): RouteParams { const newParams: RouteParams = {} for (const key in params) { const value = params[key] newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value) } return newParams}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
然后声明了大量的函数,包括addRoute、removeRoute、getRoutes等,这些函数也就是我们日常使用的addRoute、removeRoute等。
在createRouter的最后创建了一个router对象,并将其返回,该对象几乎包含了声明的所有函数。
总结
createRouter函数中声明了一些全局钩子所需的变量和很多函数,这些函数就是我们日常使用的一些方法,如addRoute、removeRoute等,在函数的最后,声明了一个router对象,前面所声明的函数多数都会被包含在这个对象里,最终会将router返回。在router中有个重要的install方法,关于install的过程可以看之前的文章,这里就不再次介绍了。
对于router中的各个函数,会在后续文章中继续介绍。